diff --git a/.github/ci-config.yml b/.github/ci-config.yml index 52a3c0a16..5d43412c6 100644 --- a/.github/ci-config.yml +++ b/.github/ci-config.yml @@ -1,4 +1,6 @@ +cmake_options: -DENABLE_ECKIT_GEO=ON dependencies: | ecmwf/ecbuild + MathisRosenhauer/libaec@master dependency_branch: develop parallelism_factor: 8 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d20c2f57..43b9355d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: # Run CI including downstream packages on self-hosted runners downstream-ci: name: downstream-ci - if: ${{ !github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci' }} + if: ${{ !github.event.pull_request.head.repo.fork && (github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci') }} uses: ecmwf-actions/downstream-ci/.github/workflows/downstream-ci.yml@main with: eckit: ecmwf/eckit@${{ github.event.pull_request.head.sha || github.sha }} @@ -34,7 +34,7 @@ jobs: private-downstream-ci: name: private-downstream-ci needs: [downstream-ci] - if: (success() || failure()) && ${{ !github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci' }} + if: ${{ (success() || failure()) && !github.event.pull_request.head.repo.fork && (github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci') }} runs-on: ubuntu-latest permissions: pull-requests: write @@ -51,7 +51,7 @@ jobs: # Build downstream packages on HPC downstream-ci-hpc: name: downstream-ci-hpc - if: ${{ !github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci' }} + if: ${{ !github.event.pull_request.head.repo.fork && (github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci') }} uses: ecmwf-actions/downstream-ci/.github/workflows/downstream-ci-hpc.yml@main with: eckit: ecmwf/eckit@${{ github.event.pull_request.head.sha || github.sha }} @@ -61,7 +61,7 @@ jobs: private-downstream-ci-hpc: name: private-downstream-ci-hpc needs: [downstream-ci-hpc] - if: (success() || failure()) && ${{ !github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci' }} + if: ${{ (success() || failure()) && !github.event.pull_request.head.repo.fork && (github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci') }} runs-on: ubuntu-latest permissions: pull-requests: write @@ -74,3 +74,21 @@ jobs: repository: private-downstream-ci event_type: downstream-ci-hpc payload: '{"eckit": "ecmwf/eckit@${{ github.event.pull_request.head.sha || github.sha }}"}' + + notify: + runs-on: ubuntu-latest + needs: + - downstream-ci + - private-downstream-ci + - downstream-ci-hpc + - private-downstream-ci-hpc + if: ${{ always() && !github.event.pull_request.head.repo.fork && (github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci') }} + steps: + - name: Trigger Teams notification + if: failure() && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') + uses: ecmwf-actions/notify-teams@v1 + with: + incoming_webhook: ${{ secrets.MS_TEAMS_INCOMING_WEBHOOK }} + needs_context: ${{ toJSON(needs) }} + notify_on: | + failure diff --git a/.github/workflows/notify-issues.yml b/.github/workflows/notify-issues.yml new file mode 100644 index 000000000..282bb705d --- /dev/null +++ b/.github/workflows/notify-issues.yml @@ -0,0 +1,14 @@ +name: Notify Issues + +on: + issues: + types: [opened, reopened] + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Notify Issues + uses: ecmwf-actions/notify-teams-issue@v1 + with: + incoming_webhook: ${{ secrets.MS_TEAMS_INCOMING_WEBHOOK }} diff --git a/.github/workflows/notify-pull-request.yml b/.github/workflows/notify-pull-request.yml new file mode 100644 index 000000000..7d1ab72bc --- /dev/null +++ b/.github/workflows/notify-pull-request.yml @@ -0,0 +1,14 @@ +name: Notify Pull Request + +on: + pull_request_target: + types: [opened, reopened] + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Notify Pull Request + uses: ecmwf-actions/notify-teams-pr@v1 + with: + incoming_webhook: ${{ secrets.MS_TEAMS_INCOMING_WEBHOOK }} diff --git a/.gitignore b/.gitignore index ecb6bf7d5..adf23fad2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +_/ .tags* CMakeLists.txt.user* *.autosave diff --git a/CMakeLists.txt b/CMakeLists.txt index b026032aa..e309f760b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,16 @@ set( THREADS_HAVE_PTHREAD_ARG FALSE ) find_package( Threads REQUIRED ) set( THREADS_LIBRARIES ${CMAKE_THREAD_LIBS_INIT} ) +ecbuild_add_option( FEATURE ECKIT_MEMORY_FACTORY_BUILDERS_DEBUG + DEFAULT OFF + DESCRIPTION "eckit::Factory builders debug" + ADVANCED ) + +ecbuild_add_option( FEATURE ECKIT_MEMORY_FACTORY_EMPTY_DESTRUCTION + DEFAULT OFF + DESCRIPTION "eckit::Factory empty destruction (system dependant)" + ADVANCED ) + ### eckit::mpi ecbuild_add_option( FEATURE MPI @@ -150,14 +160,60 @@ ecbuild_add_option( FEATURE AEC ecbuild_add_option( FEATURE XXHASH DESCRIPTION "xxHash support for hashing" ) -### Codec options +### eckit::codec ecbuild_add_option( FEATURE ECKIT_CODEC - DEFAULT OFF - DESCRIPTION "Encoding/Decoding library ported from atlas_io" ) + DEFAULT ON + DESCRIPTION "eckit::codec encoding/decoding library" ) set( eckit_CODEC_STATIC_ASSERT ON CACHE BOOL "eckit::codec static assertions" ) +### eckit::maths + +ecbuild_add_option( FEATURE CONVEX_HULL + DEFAULT OFF + DESCRIPTION "eckit::maths library convex hull/Delaunay triangulation" ) + # REQUIRED_PACKAGES "Qhull COMPONENTS C CXX" + +if( eckit_HAVE_CONVEX_HULL ) + find_package( Qhull REQUIRED CONFIG ) + + if( NOT TARGET Qhull::qhullcpp OR NOT TARGET Qhull::qhullstatic_r ) + message( FATAL_ERROR "eckit::maths ENABLE_CONVEX_HULL requires Qhull C/C++ libraries" ) + endif() + + add_library(Qhull::Qhull INTERFACE IMPORTED) + target_link_libraries(Qhull::Qhull INTERFACE Qhull::qhullcpp Qhull::qhullstatic_r ) +endif() + +### eckit::geo + +ecbuild_add_option( FEATURE ECKIT_GEO + DEFAULT OFF + DESCRIPTION "eckit::geo geometry library" ) + +ecbuild_add_option( FEATURE GEO_GRID_ORCA + DEFAULT ON + CONDITION eckit_HAVE_ECKIT_GEO AND eckit_HAVE_ECKIT_CODEC AND eckit_HAVE_LZ4 + DESCRIPTION "eckit::geo geometry library support for ORCA grids" ) + +ecbuild_add_option( FEATURE GEO_CACHING + DEFAULT OFF + CONDITION HAVE_ECKIT_GEO + DESCRIPTION "eckit::geo geometry library default caching behaviour" ) + +ecbuild_add_option( FEATURE GEO_BITREPRODUCIBLE + DEFAULT OFF + CONDITION HAVE_ECKIT_GEO + DESCRIPTION "eckit::geo geometry library bit reproducibility tests" ) + +ecbuild_add_option( FEATURE GEO_PROJECTION_PROJ_DEFAULT + DEFAULT OFF + CONDITION HAVE_ECKIT_GEO + DESCRIPTION "eckit::geo geometry library default to PROJ-based projections" ) + +set( eckit_GEO_CACHE_PATH "/tmp/cache" ) + ### LAPACK if( eckit_HAVE_MKL ) @@ -169,9 +225,10 @@ else() REQUIRED_PACKAGES "LAPACK QUIET" ) endif() -### OpenSSL (for SHA1) +### OpenSSL (for SHA1 and MD4) ecbuild_add_option( FEATURE SSL + DEFAULT OFF # We only use deprecated functionality from OpenSSL DESCRIPTION "OpenSSL support" REQUIRED_PACKAGES OpenSSL ) @@ -199,6 +256,7 @@ ecbuild_add_option( FEATURE JEMALLOC #### CUDA ecbuild_add_option( FEATURE CUDA + DEFAULT OFF CONDITION HAVE_EXPERIMENTAL DESCRIPTION "CUDA GPU linear algebra operations" REQUIRED_PACKAGES CUDA ) @@ -225,9 +283,6 @@ if( HAVE_RSYNC ) set( LIBRSYNC_LIBRARIES rsync ) endif() -ecbuild_add_option( FEATURE SANDBOX - DEFAULT OFF - DESCRIPTION "Sandbox playground for prototyping code that may never see the light of day" ) ### Performance tests @@ -252,6 +307,13 @@ ecbuild_add_option( FEATURE AIO CONDITION ${AIO_FOUND} DESCRIPTION "support for asynchronous IO") +### PROJ support + +ecbuild_add_option( FEATURE PROJ + DEFAULT OFF + DESCRIPTION "support PROJ-based projections" + REQUIRED_PACKAGES "PROJ 9.2" ) + ### c math library, needed when including "math.h" find_package( CMath ) @@ -283,15 +345,15 @@ include(cmake/compiler_warnings.cmake) # optionally handle compiler specific war set( PERSISTENT_NAMESPACE "eckit" CACHE INTERNAL "" ) # needed for generating .b files for persistent support add_subdirectory( src ) - add_subdirectory( bamboo ) add_subdirectory( doc ) +add_subdirectory( etc ) add_subdirectory( tests ) add_subdirectory( regressions ) ecbuild_add_resources( TARGET ${PROJECT_NAME}_top_files - SOURCES AUTHORS README.md NOTICE LICENSE - INSTALL ChangeLog COPYING ) + SOURCES AUTHORS README.md NOTICE LICENSE + INSTALL ChangeLog COPYING ) ############################################################################################ # finalize diff --git a/README.md b/README.md index ada2c1edb..424338622 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Now proceed with installation as follows: # Environment --- Edit as needed srcdir=$(pwd) builddir=build -installdir=$HOME/local +installdir=$HOME/.local # 1. Create the build directory: mkdir $builddir @@ -85,6 +85,3 @@ make install # 4. Check installation $installdir/bin/eckit-version ``` - - - diff --git a/VERSION b/VERSION index 61b813d5e..ac786b645 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.25.2 +1.28.3 diff --git a/cmake/FindAEC.cmake b/cmake/FindAEC.cmake index ae1d7ffad..7eb5b3339 100644 --- a/cmake/FindAEC.cmake +++ b/cmake/FindAEC.cmake @@ -18,14 +18,19 @@ # # AEC_DIR - prefix path of the AEC installation # AEC_PATH - prefix path of the AEC installation - -find_path( AEC_INCLUDE_DIR szlib.h - PATHS ${AEC_DIR} ${AEC_PATH} ENV AEC_DIR ENV AEC_PATH +# LIBAEC_DIR +# libaec_DIR +# LIBAEC_PATH +# libaec_PATH +# libaec_ROOT + +find_path( AEC_INCLUDE_DIR libaec.h + PATHS ${AEC_DIR} ${AEC_PATH} ${LIBAEC_DIR} ${libaec_DIR} ${LIBAEC_PATH} ${libaec_PATH} ${libaec_ROOT} ENV AEC_DIR ENV AEC_PATH ENV LIBAEC_DIR ENV libaec_DIR ENV LIBAEC_PATH ENV libaec_PATH ENV libaec_ROOT PATH_SUFFIXES include include/aec NO_DEFAULT_PATH ) -find_path( AEC_INCLUDE_DIR szlib.h PATH_SUFFIXES include include/aec ) +find_path( AEC_INCLUDE_DIR libaec.h PATH_SUFFIXES include include/aec ) find_library( AEC_LIBRARY NAMES aec - PATHS ${AEC_DIR} ${AEC_PATH} ENV AEC_DIR ENV AEC_PATH + PATHS ${AEC_DIR} ${AEC_PATH} ${LIBAEC_DIR} ${libaec_DIR} ${LIBAEC_PATH} ${libaec_PATH} ${libaec_ROOT} ENV AEC_DIR ENV AEC_PATH ENV LIBAEC_DIR ENV libaec_DIR ENV LIBAEC_PATH ENV libaec_PATH ENV libaec_ROOT PATH_SUFFIXES lib lib64 lib/aec lib64/aec NO_DEFAULT_PATH ) find_library( AEC_LIBRARY NAMES aec PATH_SUFFIXES lib lib64 lib/aec lib64/aec ) diff --git a/etc/CMakeLists.txt b/etc/CMakeLists.txt new file mode 100644 index 000000000..b4521afe0 --- /dev/null +++ b/etc/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(eckit) + diff --git a/etc/eckit/CMakeLists.txt b/etc/eckit/CMakeLists.txt new file mode 100644 index 000000000..de5f754ae --- /dev/null +++ b/etc/eckit/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(geo) + diff --git a/etc/eckit/geo/CMakeLists.txt b/etc/eckit/geo/CMakeLists.txt new file mode 100644 index 000000000..cb36257fe --- /dev/null +++ b/etc/eckit/geo/CMakeLists.txt @@ -0,0 +1,12 @@ +set(_files) +list(APPEND _files "grid.yaml") +list(APPEND _files "ORCA.yaml") + +set(_destination "etc/eckit/geo") + +install(FILES ${_files} DESTINATION ${_destination} PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) + +foreach(_file ${_files}) + configure_file(${_file} "${CMAKE_BINARY_DIR}/${_destination}/${_file}" COPYONLY) +endforeach() + diff --git a/etc/eckit/geo/ORCA.yaml b/etc/eckit/geo/ORCA.yaml new file mode 100644 index 000000000..a94395f12 --- /dev/null +++ b/etc/eckit/geo/ORCA.yaml @@ -0,0 +1,359 @@ +--- + +orca_url_prefix: &orca_url_prefix https://get.ecmwf.int/repository/atlas/grids/orca/v0/ +orca_validate_uid: false # Validates UID upon grid creation +orca_compute_uid: false # Recompute UID and ignore encoded UID + +grid_names: + - ORCA2_F: &ORCA2_F + type: ORCA + orca_arrangement: F + orca_name: ORCA2 + dimensions: [182, 149] + orca_uid: "174487fbace54b00d959d971e88b71e7" + url_prefix: *orca_url_prefix + url: ORCA2_F.atlas + + - ORCA2_T: &ORCA2_T + type: ORCA + orca_arrangement: T + orca_name: ORCA2 + dimensions: [182, 149] + orca_uid: "d5bde4f52ff3a9bea5629cd9ac514410" + url_prefix: *orca_url_prefix + url: ORCA2_T.atlas + + - ORCA2_U: &ORCA2_U + type: ORCA + orca_arrangement: U + orca_name: ORCA2 + dimensions: [182, 149] + orca_uid: "857f7affa3a381e3882d38d321384e49" + url_prefix: *orca_url_prefix + url: ORCA2_U.atlas + + - ORCA2_V: &ORCA2_V + type: ORCA + orca_arrangement: V + orca_name: ORCA2 + dimensions: [182, 149] + orca_uid: "ca637bc5dc9a54e2ea4b9750e1b79e6e" + url_prefix: *orca_url_prefix + url: ORCA2_V.atlas + + - ORCA2_W: &ORCA2_W + type: ORCA + orca_arrangement: W + orca_name: ORCA2 + dimensions: [182, 149] + orca_uid: "edea6f71eb558dc056b5f576d5b904f7" + url_prefix: *orca_url_prefix + url: ORCA2_T.atlas + + - ORCA1_F: &ORCA1_F + type: ORCA + orca_arrangement: F + orca_name: ORCA1 + dimensions: [362, 292] + orca_uid: "a832a12030c73928133553ec3a8d2a7e" + url_prefix: *orca_url_prefix + url: ORCA1_F.atlas + + - ORCA1_T: &ORCA1_T + type: ORCA + orca_arrangement: T + orca_name: ORCA1 + dimensions: [362, 292] + orca_uid: "f4c91b6233fe55dec992160ec12b38df" + url_prefix: *orca_url_prefix + url: ORCA1_T.atlas + + - ORCA1_U: &ORCA1_U + type: ORCA + orca_arrangement: U + orca_name: ORCA1 + dimensions: [362, 292] + orca_uid: "1b0f8d234753f910197c975c906b4da5" + url_prefix: *orca_url_prefix + url: ORCA1_U.atlas + + - ORCA1_V: &ORCA1_V + type: ORCA + orca_arrangement: V + orca_name: ORCA1 + dimensions: [362, 292] + orca_uid: "c637340454795b395f982851b840943d" + url_prefix: *orca_url_prefix + url: ORCA1_V.atlas + + - ORCA1_W: &ORCA1_W + type: ORCA + orca_arrangement: W + orca_name: ORCA1 + dimensions: [362, 292] + orca_uid: "d50061c43e83c46c3810002591ea21e1" + url_prefix: *orca_url_prefix + url: ORCA1_T.atlas + + - eORCA1_F: &eORCA1_F + type: ORCA + orca_arrangement: F + orca_name: eORCA1 + dimensions: [362, 332] + orca_uid: "3c6d95561710c6f39b394809ff6c588c" + url_prefix: *orca_url_prefix + url: eORCA1_F.atlas + + - eORCA1_T: &eORCA1_T + type: ORCA + orca_arrangement: T + orca_name: eORCA1 + dimensions: [362, 332] + orca_uid: "ba65665a9e68d1a8fa0352ecfcf8e496" + url_prefix: *orca_url_prefix + url: eORCA1_T.atlas + + - eORCA1_U: &eORCA1_U + type: ORCA + orca_arrangement: U + orca_name: eORCA1 + dimensions: [362, 332] + orca_uid: "4eb1054957dcae914e219faf9a4068e3" + url_prefix: *orca_url_prefix + url: eORCA1_U.atlas + + - eORCA1_V: &eORCA1_V + type: ORCA + orca_arrangement: V + orca_name: eORCA1 + dimensions: [362, 332] + orca_uid: "09131429766e7737c087d3a8d7073dc9" + url_prefix: *orca_url_prefix + url: eORCA1_V.atlas + + - eORCA1_W: &eORCA1_W + type: ORCA + orca_arrangement: W + orca_name: eORCA1 + dimensions: [362, 332] + orca_uid: "5c678d8f9aa2edfbf57246d11d9c1278" + url_prefix: *orca_url_prefix + url: eORCA1_T.atlas + + - ORCA025_F: &ORCA025_F + type: ORCA + orca_arrangement: F + orca_name: ORCA025 + dimensions: [1442, 1021] + orca_uid: "efbc280d8d4b6048797880da2605bacb" + url_prefix: *orca_url_prefix + url: ORCA025_F.atlas + + - ORCA025_T: &ORCA025_T + type: ORCA + orca_arrangement: T + orca_name: ORCA025 + dimensions: [1442, 1021] + orca_uid: "15c961c269ac182ca226d7195f3921ba" + url_prefix: *orca_url_prefix + url: ORCA025_T.atlas + + - ORCA025_U: &ORCA025_U + type: ORCA + orca_arrangement: U + orca_name: ORCA025 + dimensions: [1442, 1021] + orca_uid: "3f4a68bc5b54c9f867fbcc12aacc723d" + url_prefix: *orca_url_prefix + url: ORCA025_U.atlas + + - ORCA025_V: &ORCA025_V + type: ORCA + orca_arrangement: V + orca_name: ORCA025 + dimensions: [1442, 1021] + orca_uid: "9c87699ee2026c0feee07d2a972eaccd" + url_prefix: *orca_url_prefix + url: ORCA025_V.atlas + + - ORCA025_W: &ORCA025_W + type: ORCA + orca_arrangement: W + orca_name: ORCA025 + dimensions: [1442, 1021] + orca_uid: "74ca68f1c8524811f3d3aad99536adc2" + url_prefix: *orca_url_prefix + url: ORCA025_T.atlas + + - eORCA025_F: &eORCA025_F + type: ORCA + orca_arrangement: F + orca_name: eORCA025 + dimensions: [1442, 1207] + orca_uid: "770e5bbb667a253d55db8a98a3b2d3a9" + url_prefix: *orca_url_prefix + url: eORCA025_F.atlas + + - eORCA025_T: &eORCA025_T + type: ORCA + orca_arrangement: T + orca_name: eORCA025 + dimensions: [1442, 1207] + orca_uid: "983412216c9768bc794c18dc92082895" + url_prefix: *orca_url_prefix + url: eORCA025_T.atlas + + - eORCA025_U: &eORCA025_U + type: ORCA + orca_arrangement: U + orca_name: eORCA025 + dimensions: [1442, 1207] + orca_uid: "b1b2922e9b57ee9c6eeddad218b6e4f3" + url_prefix: *orca_url_prefix + url: eORCA025_U.atlas + + - eORCA025_V: &eORCA025_V + type: ORCA + orca_arrangement: V + orca_name: eORCA025 + dimensions: [1442, 1207] + orca_uid: "9b06bf73a8f14e927bd9b0f1f0c04f74" + url_prefix: *orca_url_prefix + url: eORCA025_V.atlas + + - eORCA025_W: &eORCA025_W + type: ORCA + orca_arrangement: W + orca_name: eORCA025 + dimensions: [1442, 1207] + orca_uid: "4a1ba3b11b8888aefc96992b6b1cab62" + url_prefix: *orca_url_prefix + url: eORCA025_T.atlas + + - ORCA12_F: &ORCA12_F + type: ORCA + orca_arrangement: F + orca_name: ORCA12 + dimensions: [4322, 3059] + orca_uid: "29693ad8a7af3ae3ee0f02d090f0ec7b" + url_prefix: *orca_url_prefix + url: ORCA12_F.atlas + + - ORCA12_T: &ORCA12_T + type: ORCA + orca_arrangement: T + orca_name: ORCA12 + dimensions: [4322, 3059] + orca_uid: "b117d01170ac77bca68560ab10e559de" + url_prefix: *orca_url_prefix + url: ORCA12_T.atlas + + - ORCA12_U: &ORCA12_U + type: ORCA + orca_arrangement: U + orca_name: ORCA12 + dimensions: [4322, 3059] + orca_uid: "fff193b92d94d03e847ff2fa62b493f4" + url_prefix: *orca_url_prefix + url: ORCA12_U.atlas + + - ORCA12_V: &ORCA12_V + type: ORCA + orca_arrangement: V + orca_name: ORCA12 + dimensions: [4322, 3059] + orca_uid: "986e3450774b716f6e75c1987e370b10" + url_prefix: *orca_url_prefix + url: ORCA12_V.atlas + + - ORCA12_W: &ORCA12_W + type: ORCA + orca_arrangement: W + orca_name: ORCA12 + dimensions: [4322, 3059] + orca_uid: "ccfe953619a8dd49a7f765923882a274" + url_prefix: *orca_url_prefix + url: ORCA12_T.atlas + + - eORCA12_F: &eORCA12_F + type: ORCA + orca_arrangement: F + orca_name: eORCA12 + dimensions: [4322, 3606] + orca_uid: "25da53ed581b3931fa310840fa9aefd9" + url_prefix: *orca_url_prefix + url: eORCA12_F.atlas + + - eORCA12_T: &eORCA12_T + type: ORCA + orca_arrangement: T + orca_name: eORCA12 + dimensions: [4322, 3606] + orca_uid: "1553b66f5885cf5f83ad4b4fdf25f460" + url_prefix: *orca_url_prefix + url: eORCA12_T.atlas + + - eORCA12_U: &eORCA12_U + type: ORCA + orca_arrangement: U + orca_name: eORCA12 + dimensions: [4322, 3606] + orca_uid: "3e87c826643da440b4e9d9f67588a576" + url_prefix: *orca_url_prefix + url: eORCA12_U.atlas + + - eORCA12_V: &eORCA12_V + type: ORCA + orca_arrangement: V + orca_name: eORCA12 + dimensions: [4322, 3606] + orca_uid: "cc1e3fc06a2cd18c0653e557510b8a71" + url_prefix: *orca_url_prefix + url: eORCA12_V.atlas + + - eORCA12_W: &eORCA12_W + type: ORCA + orca_arrangement: W + orca_name: eORCA12 + dimensions: [4322, 3606] + orca_uid: "462469edbd0e0586a0cf17424cc58c89" + url_prefix: *orca_url_prefix + url: eORCA12_T.atlas + +grid_uids: + - 174487fbace54b00d959d971e88b71e7: *ORCA2_F + - d5bde4f52ff3a9bea5629cd9ac514410: *ORCA2_T + - 857f7affa3a381e3882d38d321384e49: *ORCA2_U + - ca637bc5dc9a54e2ea4b9750e1b79e6e: *ORCA2_V + - edea6f71eb558dc056b5f576d5b904f7: *ORCA2_W + - a832a12030c73928133553ec3a8d2a7e: *ORCA1_F + - f4c91b6233fe55dec992160ec12b38df: *ORCA1_T + - 1b0f8d234753f910197c975c906b4da5: *ORCA1_U + - c637340454795b395f982851b840943d: *ORCA1_V + - d50061c43e83c46c3810002591ea21e1: *ORCA1_W + - 3c6d95561710c6f39b394809ff6c588c: *eORCA1_F + - ba65665a9e68d1a8fa0352ecfcf8e496: *eORCA1_T + - 4eb1054957dcae914e219faf9a4068e3: *eORCA1_U + - 09131429766e7737c087d3a8d7073dc9: *eORCA1_V + - 5c678d8f9aa2edfbf57246d11d9c1278: *eORCA1_W + - efbc280d8d4b6048797880da2605bacb: *ORCA025_F + - 15c961c269ac182ca226d7195f3921ba: *ORCA025_T + - 3f4a68bc5b54c9f867fbcc12aacc723d: *ORCA025_U + - 9c87699ee2026c0feee07d2a972eaccd: *ORCA025_V + - 74ca68f1c8524811f3d3aad99536adc2: *ORCA025_W + - 770e5bbb667a253d55db8a98a3b2d3a9: *eORCA025_F + - 983412216c9768bc794c18dc92082895: *eORCA025_T + - b1b2922e9b57ee9c6eeddad218b6e4f3: *eORCA025_U + - 9b06bf73a8f14e927bd9b0f1f0c04f74: *eORCA025_V + - 4a1ba3b11b8888aefc96992b6b1cab62: *eORCA025_W + - 29693ad8a7af3ae3ee0f02d090f0ec7b: *ORCA12_F + - b117d01170ac77bca68560ab10e559de: *ORCA12_T + - fff193b92d94d03e847ff2fa62b493f4: *ORCA12_U + - 986e3450774b716f6e75c1987e370b10: *ORCA12_V + - ccfe953619a8dd49a7f765923882a274: *ORCA12_W + - 25da53ed581b3931fa310840fa9aefd9: *eORCA12_F + - 1553b66f5885cf5f83ad4b4fdf25f460: *eORCA12_T + - 3e87c826643da440b4e9d9f67588a576: *eORCA12_U + - cc1e3fc06a2cd18c0653e557510b8a71: *eORCA12_V + - 462469edbd0e0586a0cf17424cc58c89: *eORCA12_W + diff --git a/etc/eckit/geo/grid.yaml b/etc/eckit/geo/grid.yaml new file mode 100644 index 000000000..cf0abd94b --- /dev/null +++ b/etc/eckit/geo/grid.yaml @@ -0,0 +1,19 @@ +--- + +grid_names: + - LAEA-EFAS-5km: + type: lambert_azimuthal_equal_area + proj: +proj=laea +lat_0=52 +lon_0=10 +ellps=GRS80 +units=m +no_defs + figure: grs80 + first_lonlat: [-35.034024, 66.982143] + grid: [5000., 5000.] + shape: [1000, 950] + + - SMUFF-OPERA-2km: + type: lambert_azimuthal_equal_area + proj: +proj=laea +lat_0=55 +lon_0=10 +ellps=WGS84 +units=m +no_defs + figure: wgs84 + first_lonlat: [-39.535385563614, 67.035186732680] + grid: [2000., 2000.] + shape: [1900, 2200] + diff --git a/src/eckit/CMakeLists.txt b/src/eckit/CMakeLists.txt index 12fead5ba..d2817cc0b 100644 --- a/src/eckit/CMakeLists.txt +++ b/src/eckit/CMakeLists.txt @@ -82,208 +82,205 @@ configure_file( eckit_version.cc.in eckit_version.cc ) list( APPEND eckit_srcs eckit.h deprecated.h ${CMAKE_CURRENT_BINARY_DIR}/eckit_version.cc ) list( APPEND eckit_container_srcs - -container/BSPTree.h -container/BTree.cc -container/BTree.h -container/BloomFilter.cc -container/BloomFilter.h -container/Cache.h -container/CacheLRU.cc -container/CacheLRU.h -container/CacheManager.cc -container/CacheManager.h -container/ClassExtent.h -container/DenseMap.h -container/DenseSet.h -container/KDMapped.cc -container/KDMapped.h -container/KDMemory.h -container/KDTree.h -container/MappedArray.cc -container/MappedArray.h -container/Queue.h -container/Recycler.h -container/SharedMemArray.cc -container/SharedMemArray.h -container/StatCollector.h -container/Trie.cc -container/Trie.h -container/bsptree/BSPHyperPlane.h -container/bsptree/BSPNode.cc -container/bsptree/BSPNode.h -container/kdtree/KDNode.cc -container/kdtree/KDNode.cc -container/kdtree/KDNode.h -container/sptree/SPIterator.h -container/sptree/SPMetadata.h -container/sptree/SPNode.h -container/sptree/SPNodeInfo.h -container/sptree/SPNodeQueue.h -container/sptree/SPTree.h -container/sptree/SPValue.h + container/BSPTree.h + container/BTree.cc + container/BTree.h + container/BloomFilter.cc + container/BloomFilter.h + container/Cache.h + container/CacheLRU.cc + container/CacheLRU.h + container/CacheManager.cc + container/CacheManager.h + container/ClassExtent.h + container/DenseMap.h + container/DenseSet.h + container/KDMapped.cc + container/KDMapped.h + container/KDMemory.h + container/KDTree.h + container/MappedArray.cc + container/MappedArray.h + container/Queue.h + container/Recycler.h + container/SharedMemArray.cc + container/SharedMemArray.h + container/StatCollector.h + container/Trie.cc + container/Trie.h + container/bsptree/BSPHyperPlane.h + container/bsptree/BSPNode.cc + container/bsptree/BSPNode.h + container/kdtree/KDNode.cc + container/kdtree/KDNode.h + container/sptree/SPIterator.h + container/sptree/SPMetadata.h + container/sptree/SPNode.h + container/sptree/SPNodeInfo.h + container/sptree/SPNodeQueue.h + container/sptree/SPTree.h + container/sptree/SPValue.h ) list( APPEND eckit_io_srcs -io/AsyncHandle.cc -io/AsyncHandle.h -io/AIOHandle.cc -io/AIOHandle.h -io/AutoCloser.h -io/Base64.cc -io/Base64.h -io/BitIO.cc -io/BitIO.h -io/Buffer.cc -io/Buffer.h -io/BufferCache.cc -io/BufferCache.h -io/BufferList.cc -io/BufferList.h -io/BufferedHandle.cc -io/BufferedHandle.h -io/PeekHandle.cc -io/PeekHandle.h -io/SeekableHandle.cc -io/SeekableHandle.h -io/CircularBuffer.cc -io/CircularBuffer.h -io/CommandStream.cc -io/CommandStream.h -io/Compress.cc -io/Compress.h -io/DataHandle.cc -io/DataHandle.h -io/DblBuffer.cc -io/DblBuffer.h -io/EmptyHandle.cc -io/EmptyHandle.h -io/FDataSync.cc -io/FDataSync.h -io/FTPHandle.cc -io/FTPHandle.h -io/FileBase.cc -io/FileBase.h -io/FileDescHandle.cc -io/FileDescHandle.h -io/FileHandle.cc -io/FileHandle.h -io/FOpenDataHandle.cc -io/MMappedFileHandle.cc -io/MMappedFileHandle.h -io/FileLock.cc -io/FileLock.h -io/FileLocker.cc -io/FileLocker.h -io/FilePool.cc -io/FilePool.h -io/FileHandle.cc -io/FileHandle.h -io/HandleBuf.cc -io/HandleBuf.h -io/HandleHolder.cc -io/HandleHolder.h -io/Length.cc -io/Length.h -io/MemoryHandle.cc -io/MemoryHandle.h -io/MoverTransfer.cc -io/MoverTransfer.h -io/MoverTransferSelection.cc -io/MoverTransferSelection.h -io/MultiHandle.cc -io/MultiHandle.h -io/Offset.cc -io/Offset.h -io/PartFileHandle.cc -io/PartFileHandle.h -io/PartHandle.cc -io/PartHandle.h -io/PipeHandle.cc -io/PipeHandle.h -io/Pipeline.cc -io/Pipeline.h -io/PooledFile.cc -io/PooledFile.h -io/PooledHandle.cc -io/PooledHandle.h -io/PooledFileDescriptor.cc -io/PooledFileDescriptor.h -io/RawFileHandle.cc -io/RawFileHandle.h -io/ResizableBuffer.h -io/Select.cc -io/Select.h -io/SharedBuffer.cc -io/SharedBuffer.h -io/SharedHandle.cc -io/SharedHandle.h -io/SockBuf.cc -io/SockBuf.h -io/StatsHandle.cc -io/StatsHandle.h -io/StdFile.cc -io/StdFile.h -io/StdPipe.cc -io/StdPipe.h -io/StdioBuf.cc -io/StdioBuf.h -io/TCPHandle.cc -io/TCPHandle.h -io/MultiSocketHandle.cc -io/MultiSocketHandle.h -io/TCPSocketHandle.cc -io/TCPSocketHandle.h -io/TeeHandle.cc -io/TeeHandle.h -io/TransferWatcher.cc -io/TransferWatcher.h -io/cluster/ClusterDisks.cc -io/cluster/ClusterDisks.h -io/cluster/ClusterNode.cc -io/cluster/ClusterNode.h -io/cluster/ClusterNodes.cc -io/cluster/ClusterNodes.h -io/cluster/NodeInfo.cc -io/cluster/NodeInfo.h + io/AIOHandle.cc + io/AIOHandle.h + io/AsyncHandle.cc + io/AsyncHandle.h + io/AutoCloser.h + io/Base64.cc + io/Base64.h + io/BitIO.cc + io/BitIO.h + io/Buffer.cc + io/Buffer.h + io/BufferCache.cc + io/BufferCache.h + io/BufferList.cc + io/BufferList.h + io/BufferedHandle.cc + io/BufferedHandle.h + io/CircularBuffer.cc + io/CircularBuffer.h + io/CommandStream.cc + io/CommandStream.h + io/Compress.cc + io/Compress.h + io/DataHandle.cc + io/DataHandle.h + io/DblBuffer.cc + io/DblBuffer.h + io/EmptyHandle.cc + io/EmptyHandle.h + io/FDataSync.cc + io/FDataSync.h + io/FOpenDataHandle.cc + io/FTPHandle.cc + io/FTPHandle.h + io/FileBase.cc + io/FileBase.h + io/FileDescHandle.cc + io/FileDescHandle.h + io/FileHandle.cc + io/FileHandle.h + io/FileLock.cc + io/FileLock.h + io/FileLocker.cc + io/FileLocker.h + io/FilePool.cc + io/FilePool.h + io/HandleBuf.cc + io/HandleBuf.h + io/HandleHolder.cc + io/HandleHolder.h + io/Length.cc + io/Length.h + io/MMappedFileHandle.cc + io/MMappedFileHandle.h + io/MemoryHandle.cc + io/MemoryHandle.h + io/MoverTransfer.cc + io/MoverTransfer.h + io/MoverTransferSelection.cc + io/MoverTransferSelection.h + io/MultiHandle.cc + io/MultiHandle.h + io/MultiSocketHandle.cc + io/MultiSocketHandle.h + io/Offset.cc + io/Offset.h + io/PartFileHandle.cc + io/PartFileHandle.h + io/PartHandle.cc + io/PartHandle.h + io/PeekHandle.cc + io/PeekHandle.h + io/PipeHandle.cc + io/PipeHandle.h + io/Pipeline.cc + io/Pipeline.h + io/PooledFile.cc + io/PooledFile.h + io/PooledFileDescriptor.cc + io/PooledFileDescriptor.h + io/PooledHandle.cc + io/PooledHandle.h + io/RawFileHandle.cc + io/RawFileHandle.h + io/ResizableBuffer.h + io/SeekableHandle.cc + io/SeekableHandle.h + io/Select.cc + io/Select.h + io/SharedBuffer.cc + io/SharedBuffer.h + io/SharedHandle.cc + io/SharedHandle.h + io/SockBuf.cc + io/SockBuf.h + io/StatsHandle.cc + io/StatsHandle.h + io/StdFile.cc + io/StdFile.h + io/StdPipe.cc + io/StdPipe.h + io/StdioBuf.cc + io/StdioBuf.h + io/TCPHandle.cc + io/TCPHandle.h + io/TCPSocketHandle.cc + io/TCPSocketHandle.h + io/TeeHandle.cc + io/TeeHandle.h + io/TransferWatcher.cc + io/TransferWatcher.h + io/cluster/ClusterDisks.cc + io/cluster/ClusterDisks.h + io/cluster/ClusterNode.cc + io/cluster/ClusterNode.h + io/cluster/ClusterNodes.cc + io/cluster/ClusterNodes.h + io/cluster/NodeInfo.cc + io/cluster/NodeInfo.h ) if(HAVE_CURL) -list(APPEND eckit_io_srcs -io/EasyCURL.cc -io/EasyCURL.h -io/URLHandle.cc -io/URLHandle.h) + list(APPEND eckit_io_srcs + io/EasyCURL.cc + io/EasyCURL.h + io/URLHandle.cc + io/URLHandle.h + ) endif() list(APPEND eckit_message_srcs -message/Decoder.cc -message/Decoder.h -message/Message.cc -message/Message.h -message/MessageContent.cc -message/MessageContent.h -message/Reader.cc -message/Reader.h -message/Splitter.cc -message/Splitter.h + message/Decoder.cc + message/Decoder.h + message/Message.cc + message/Message.h + message/MessageContent.cc + message/MessageContent.h + message/Reader.cc + message/Reader.h + message/Splitter.cc + message/Splitter.h ) if(HAVE_RADOS) -list( APPEND eckit_io_srcs -io/rados/RadosHandle.cc -io/rados/RadosHandle.h -io/rados/RadosCluster.h -io/rados/RadosCluster.cc -io/rados/RadosReadHandle.cc -io/rados/RadosReadHandle.h -io/rados/RadosWriteHandle.cc -io/rados/RadosWriteHandle.h -io/rados/RadosObject.h -io/rados/RadosObject.cc -io/rados/RadosAttributes.h -io/rados/RadosAttributes.cc -) + list( APPEND eckit_io_srcs + io/rados/RadosHandle.cc + io/rados/RadosHandle.h + io/rados/RadosCluster.h + io/rados/RadosCluster.cc + io/rados/RadosReadHandle.cc + io/rados/RadosReadHandle.h + io/rados/RadosWriteHandle.cc + io/rados/RadosWriteHandle.h + io/rados/RadosObject.h + io/rados/RadosObject.cc + io/rados/RadosAttributes.h + io/rados/RadosAttributes.cc + ) endif() if(HAVE_AWS_S3) @@ -315,41 +312,41 @@ io/s3/S3URIManager.h endif(HAVE_AWS_S3) list( APPEND eckit_filesystem_srcs -filesystem/BasePathName.cc -filesystem/BasePathName.h -filesystem/BasePathNameT.cc -filesystem/BasePathNameT.h -filesystem/FileMode.cc -filesystem/FileMode.h -filesystem/FileSpace.cc -filesystem/FileSpace.h -filesystem/FileSpaceStrategies.cc -filesystem/FileSpaceStrategies.h -filesystem/FileSystem.cc -filesystem/FileSystem.h -filesystem/FileSystemSize.h -filesystem/LocalPathName.cc -filesystem/LocalPathName.h -filesystem/PathExpander.cc -filesystem/PathExpander.h -filesystem/PathName.cc -filesystem/PathName.h -filesystem/PathNameFactory.cc -filesystem/PathNameFactory.h -filesystem/TempFile.cc -filesystem/TempFile.h -filesystem/TmpDir.cc -filesystem/TmpDir.h -filesystem/StdDir.cc -filesystem/StdDir.h -filesystem/TmpFile.cc -filesystem/TmpFile.h -filesystem/URI.cc -filesystem/URI.h -filesystem/URIManager.cc -filesystem/URIManager.h -filesystem/LocalFileManager.cc -filesystem/LocalFileManager.h + filesystem/BasePathName.cc + filesystem/BasePathName.h + filesystem/BasePathNameT.cc + filesystem/BasePathNameT.h + filesystem/FileMode.cc + filesystem/FileMode.h + filesystem/FileSpace.cc + filesystem/FileSpace.h + filesystem/FileSpaceStrategies.cc + filesystem/FileSpaceStrategies.h + filesystem/FileSystem.cc + filesystem/FileSystem.h + filesystem/FileSystemSize.h + filesystem/LocalPathName.cc + filesystem/LocalPathName.h + filesystem/PathExpander.cc + filesystem/PathExpander.h + filesystem/PathName.cc + filesystem/PathName.h + filesystem/PathNameFactory.cc + filesystem/PathNameFactory.h + filesystem/TempFile.cc + filesystem/TempFile.h + filesystem/TmpDir.cc + filesystem/TmpDir.h + filesystem/StdDir.cc + filesystem/StdDir.h + filesystem/TmpFile.cc + filesystem/TmpFile.h + filesystem/URI.cc + filesystem/URI.h + filesystem/URIManager.cc + filesystem/URIManager.h + filesystem/LocalFileManager.cc + filesystem/LocalFileManager.h ) list( APPEND eckit_thread_srcs @@ -371,407 +368,405 @@ list( APPEND eckit_thread_srcs ) list( APPEND eckit_config_srcs -config/Configurable.cc -config/Configurable.h -config/Configuration.cc -config/Configuration.h -config/Configured.cc -config/Configured.h -config/EtcTable.cc -config/EtcTable.h -config/JSONConfiguration.h -config/LibEcKit.cc -config/LibEcKit.h -config/LocalConfiguration.cc -config/LocalConfiguration.h -config/Parametrisation.cc -config/Parametrisation.h -config/Resource.h -config/ResourceBase.cc -config/ResourceBase.h -config/ResourceMgr.cc -config/ResourceMgr.h -config/YAMLConfiguration.cc -config/YAMLConfiguration.h + config/Configurable.cc + config/Configurable.h + config/Configuration.cc + config/Configuration.h + config/Configured.cc + config/Configured.h + config/EtcTable.cc + config/EtcTable.h + config/JSONConfiguration.h + config/LibEcKit.cc + config/LibEcKit.h + config/LocalConfiguration.cc + config/LocalConfiguration.h + config/Parametrisation.cc + config/Parametrisation.h + config/Resource.h + config/ResourceBase.cc + config/ResourceBase.h + config/ResourceMgr.cc + config/ResourceMgr.h + config/YAMLConfiguration.cc + config/YAMLConfiguration.h ) list( APPEND eckit_runtime_srcs -runtime/Application.cc -runtime/Application.h -runtime/Dispatcher.h -runtime/Library.cc -runtime/Library.h -runtime/Main.cc -runtime/Main.h -runtime/Metrics.cc -runtime/Metrics.h -runtime/Monitor.cc -runtime/Monitor.h -runtime/Monitorable.cc -runtime/Monitorable.h -runtime/Pipe.h -runtime/PipeApplication.cc -runtime/PipeApplication.h -runtime/PipeHandler.cc -runtime/PipeHandler.h -runtime/ProcessControler.cc -runtime/ProcessControler.h -runtime/ProducerConsumer.h -runtime/Telemetry.cc -runtime/Telemetry.h -runtime/SessionID.cc -runtime/SessionID.h -runtime/Task.cc -runtime/Task.h -runtime/TaskID.h -runtime/TaskInfo.cc -runtime/TaskInfo.h -runtime/Tool.cc -runtime/Tool.h + runtime/Application.cc + runtime/Application.h + runtime/Dispatcher.h + runtime/Library.cc + runtime/Library.h + runtime/Main.cc + runtime/Main.h + runtime/Metrics.cc + runtime/Metrics.h + runtime/Monitor.cc + runtime/Monitor.h + runtime/Monitorable.cc + runtime/Monitorable.h + runtime/Pipe.h + runtime/PipeApplication.cc + runtime/PipeApplication.h + runtime/PipeHandler.cc + runtime/PipeHandler.h + runtime/ProcessControler.cc + runtime/ProcessControler.h + runtime/ProducerConsumer.h + runtime/Telemetry.cc + runtime/Telemetry.h + runtime/SessionID.cc + runtime/SessionID.h + runtime/Task.cc + runtime/Task.h + runtime/TaskID.h + runtime/TaskInfo.cc + runtime/TaskInfo.h + runtime/Tool.cc + runtime/Tool.h ) list( APPEND eckit_log_srcs -log/BigNum.cc -log/BigNum.h -log/Bytes.cc -log/Bytes.h -log/CallbackTarget.cc -log/CallbackTarget.h -log/Channel.cc -log/Channel.h -log/ChannelBuffer.cc -log/ChannelBuffer.h -log/CodeLocation.cc -log/CodeLocation.h -log/Colour.cc -log/Colour.h -log/ColouringTarget.cc -log/ColouringTarget.h -log/ETA.cc -log/ETA.h -log/FileTarget.cc -log/FileTarget.h -log/IndentTarget.cc -log/IndentTarget.h -log/JSON.cc -log/JSON.h -log/LineBasedTarget.cc -log/LineBasedTarget.h -log/Log.cc -log/Log.h -log/LogTarget.cc -log/LogTarget.h -log/MessageTarget.cc -log/MessageTarget.h -log/MonitorTarget.cc -log/MonitorTarget.h -log/Number.cc -log/Number.h -log/OStreamTarget.cc -log/OStreamTarget.h -log/Plural.h -log/PrefixTarget.cc -log/PrefixTarget.h -log/Progress.cc -log/Progress.h -log/ProgressTimer.cc -log/ProgressTimer.h -log/ResourceUsage.cc -log/ResourceUsage.h -log/RotationTarget.cc -log/RotationTarget.h -log/SavedStatus.cc -log/SavedStatus.h -log/Seconds.cc -log/Seconds.h -log/Statistics.cc -log/Statistics.h -log/StatusTarget.cc -log/StatusTarget.h -log/SysLog.cc -log/SysLog.h -log/TeeTarget.cc -log/TeeTarget.h -log/TimeStamp.cc -log/TimeStamp.h -log/TimeStampTarget.cc -log/TimeStampTarget.h -log/Timer.cc -log/Timer.h -log/TraceTimer.h -log/UserChannel.cc -log/UserChannel.h -log/WrapperTarget.cc -log/WrapperTarget.h + log/BigNum.cc + log/BigNum.h + log/Bytes.cc + log/Bytes.h + log/CallbackTarget.cc + log/CallbackTarget.h + log/Channel.cc + log/Channel.h + log/ChannelBuffer.cc + log/ChannelBuffer.h + log/CodeLocation.cc + log/CodeLocation.h + log/Colour.cc + log/Colour.h + log/ColouringTarget.cc + log/ColouringTarget.h + log/ETA.cc + log/ETA.h + log/FileTarget.cc + log/FileTarget.h + log/IndentTarget.cc + log/IndentTarget.h + log/JSON.cc + log/JSON.h + log/LineBasedTarget.cc + log/LineBasedTarget.h + log/Log.cc + log/Log.h + log/LogTarget.cc + log/LogTarget.h + log/MessageTarget.cc + log/MessageTarget.h + log/MonitorTarget.cc + log/MonitorTarget.h + log/Number.cc + log/Number.h + log/OStreamTarget.cc + log/OStreamTarget.h + log/Plural.h + log/PrefixTarget.cc + log/PrefixTarget.h + log/Progress.cc + log/Progress.h + log/ProgressTimer.cc + log/ProgressTimer.h + log/ResourceUsage.cc + log/ResourceUsage.h + log/RotationTarget.cc + log/RotationTarget.h + log/SavedStatus.cc + log/SavedStatus.h + log/Seconds.cc + log/Seconds.h + log/Statistics.cc + log/Statistics.h + log/StatusTarget.cc + log/StatusTarget.h + log/SysLog.cc + log/SysLog.h + log/TeeTarget.cc + log/TeeTarget.h + log/TimeStamp.cc + log/TimeStamp.h + log/TimeStampTarget.cc + log/TimeStampTarget.h + log/Timer.cc + log/Timer.h + log/TraceTimer.h + log/UserChannel.cc + log/UserChannel.h + log/WrapperTarget.cc + log/WrapperTarget.h ) list( APPEND eckit_exception_srcs -exception/Exceptions.cc -exception/Exceptions.h + exception/Exceptions.cc + exception/Exceptions.h ) list( APPEND eckit_types_srcs -types/ClimateDate.cc -types/ClimateDate.h -types/Coord.cc -types/Coord.h -types/Date.cc -types/Date.h -types/DateTime.cc -types/DateTime.h -types/DayOfYear.cc -types/DayOfYear.h -types/Double.cc -types/Double.h -types/FixedString.h -types/FloatCompare.cc -types/FloatCompare.h -types/Fraction.cc -types/Fraction.h -types/Grid.cc -types/Grid.h -types/Hour.cc -types/Hour.h -types/Month.cc -types/Month.h -types/Time.cc -types/Time.h -types/TimeInterval.cc -types/TimeInterval.h -types/Types.cc -types/Types.h -types/UUID.cc -types/UUID.h -types/SemanticVersion.cc -types/SemanticVersion.h -types/VerifyingDate.cc -types/VerifyingDate.h + types/ClimateDate.cc + types/ClimateDate.h + types/Coord.cc + types/Coord.h + types/Date.cc + types/Date.h + types/DateTime.cc + types/DateTime.h + types/DayOfYear.cc + types/DayOfYear.h + types/Double.cc + types/Double.h + types/FixedString.h + types/FloatCompare.cc + types/FloatCompare.h + types/Fraction.cc + types/Fraction.h + types/Grid.cc + types/Grid.h + types/Hour.cc + types/Hour.h + types/Month.cc + types/Month.h + types/SemanticVersion.cc + types/SemanticVersion.h + types/Time.cc + types/Time.h + types/TimeInterval.cc + types/TimeInterval.h + types/Types.cc + types/Types.h + types/UUID.cc + types/UUID.h + types/VerifyingDate.cc + types/VerifyingDate.h ) list( APPEND eckit_parser_srcs -parser/CSVParser.cc -parser/CSVParser.h -parser/JSON.h -parser/JSONParser.cc -parser/JSONParser.h -parser/ObjectParser.cc -parser/ObjectParser.h -parser/StreamParser.cc -parser/StreamParser.h -parser/YAMLParser.cc -parser/YAMLParser.h + parser/CSVParser.cc + parser/CSVParser.h + parser/JSON.h + parser/JSONParser.cc + parser/JSONParser.h + parser/ObjectParser.cc + parser/ObjectParser.h + parser/StreamParser.cc + parser/StreamParser.h + parser/YAMLParser.cc + parser/YAMLParser.h ) list( APPEND eckit_value_srcs -value/BoolContent.cc -value/BoolContent.h -value/CompositeParams.cc -value/CompositeParams.h -value/Content.cc -value/Content.h -value/DateContent.cc -value/DateContent.h -value/DateTimeContent.cc -value/DateTimeContent.h -value/DispatchParams.h -value/DoubleContent.cc -value/DoubleContent.h -value/Expression.h -value/ListContent.cc -value/ListContent.h -value/MapContent.cc -value/MapContent.h -value/NilContent.cc -value/NilContent.h -value/NumberContent.cc -value/NumberContent.h -value/OrderedMapContent.cc -value/OrderedMapContent.h -value/Params.cc -value/Params.h -value/Properties.cc -value/Properties.h -value/ScopeParams.cc -value/ScopeParams.h -value/StringContent.cc -value/StringContent.h -value/TimeContent.cc -value/TimeContent.h -value/Value.cc -value/Value.h + value/BoolContent.cc + value/BoolContent.h + value/CompositeParams.cc + value/CompositeParams.h + value/Content.cc + value/Content.h + value/DateContent.cc + value/DateContent.h + value/DateTimeContent.cc + value/DateTimeContent.h + value/DispatchParams.h + value/DoubleContent.cc + value/DoubleContent.h + value/Expression.h + value/ListContent.cc + value/ListContent.h + value/MapContent.cc + value/MapContent.h + value/NilContent.cc + value/NilContent.h + value/NumberContent.cc + value/NumberContent.h + value/OrderedMapContent.cc + value/OrderedMapContent.h + value/Params.cc + value/Params.h + value/Properties.cc + value/Properties.h + value/ScopeParams.cc + value/ScopeParams.h + value/StringContent.cc + value/StringContent.h + value/TimeContent.cc + value/TimeContent.h + value/Value.cc + value/Value.h ) list( APPEND eckit_os_srcs -os/AutoAlarm.cc -os/AutoAlarm.h -os/AutoUmask.h -os/BackTrace.cc -os/BackTrace.h -os/Password.cc -os/Password.h -os/SemLocker.cc -os/SemLocker.h -os/Semaphore.cc -os/Semaphore.h -os/SharedInt.cc -os/SharedInt.h -os/SignalHandler.cc -os/SignalHandler.h -os/Stat.h -os/System.cc -os/System.h + os/AutoAlarm.cc + os/AutoAlarm.h + os/AutoUmask.h + os/BackTrace.cc + os/BackTrace.h + os/Password.cc + os/Password.h + os/SemLocker.cc + os/SemLocker.h + os/Semaphore.cc + os/Semaphore.h + os/SharedInt.cc + os/SharedInt.h + os/SignalHandler.cc + os/SignalHandler.h + os/Stat.h + os/System.cc + os/System.h ) list( APPEND eckit_net_srcs -net/Connector.cc -net/Connector.h -net/Endpoint.cc -net/Endpoint.h -net/HttpHeader.cc -net/HttpHeader.h -net/IPAddress.cc -net/IPAddress.h -net/NetMask.cc -net/NetMask.h -net/NetService.cc -net/NetService.h -net/NetUser.cc -net/NetUser.h -net/Port.cc -net/Port.h -net/ProxiedTCPClient.cc -net/ProxiedTCPClient.h -net/ProxiedTCPServer.cc -net/ProxiedTCPServer.h -net/SocketOptions.cc -net/SocketOptions.h -net/TCPClient.cc -net/TCPClient.h -net/TCPServer.cc -net/TCPServer.h -net/TCPSocket.cc -net/TCPSocket.h -net/MultiSocket.cc -net/MultiSocket.h -net/TCPStream.cc -net/TCPStream.h -net/Telnet.cc -net/Telnet.h -net/TelnetUser.cc -net/TelnetUser.h -net/Telnetable.cc -net/Telnetable.h -net/UDPClient.cc -net/UDPClient.h -net/UDPServer.cc -net/UDPServer.h + net/Connector.cc + net/Connector.h + net/Endpoint.cc + net/Endpoint.h + net/HttpHeader.cc + net/HttpHeader.h + net/IPAddress.cc + net/IPAddress.h + net/MultiSocket.cc + net/MultiSocket.h + net/NetMask.cc + net/NetMask.h + net/NetService.cc + net/NetService.h + net/NetUser.cc + net/NetUser.h + net/Port.cc + net/Port.h + net/ProxiedTCPClient.cc + net/ProxiedTCPClient.h + net/ProxiedTCPServer.cc + net/ProxiedTCPServer.h + net/SocketOptions.cc + net/SocketOptions.h + net/TCPClient.cc + net/TCPClient.h + net/TCPServer.cc + net/TCPServer.h + net/TCPSocket.cc + net/TCPSocket.h + net/TCPStream.cc + net/TCPStream.h + net/Telnet.cc + net/Telnet.h + net/TelnetUser.cc + net/TelnetUser.h + net/Telnetable.cc + net/Telnetable.h + net/UDPClient.cc + net/UDPClient.h + net/UDPServer.cc + net/UDPServer.h ) list( APPEND eckit_serialisation_srcs -serialisation/BadTag.cc -serialisation/BadTag.h -serialisation/FileStream.cc -serialisation/FileStream.h -serialisation/FstreamStream.h -serialisation/HandleStream.h -serialisation/IfstreamStream.h -serialisation/MemoryStream.cc -serialisation/MemoryStream.h -serialisation/PipeStream.cc -serialisation/PipeStream.h -serialisation/Reanimator.cc -serialisation/Reanimator.h -serialisation/ReanimatorBase.cc -serialisation/ResizableMemoryStream.cc -serialisation/ResizableMemoryStream.h -serialisation/Stream.cc -serialisation/Stream.h -serialisation/Streamable.cc -serialisation/Streamable.h + serialisation/BadTag.cc + serialisation/BadTag.h + serialisation/FileStream.cc + serialisation/FileStream.h + serialisation/FstreamStream.h + serialisation/HandleStream.h + serialisation/IfstreamStream.h + serialisation/MemoryStream.cc + serialisation/MemoryStream.h + serialisation/PipeStream.cc + serialisation/PipeStream.h + serialisation/Reanimator.cc + serialisation/Reanimator.h + serialisation/ReanimatorBase.cc + serialisation/ResizableMemoryStream.cc + serialisation/ResizableMemoryStream.h + serialisation/Stream.cc + serialisation/Stream.h + serialisation/Streamable.cc + serialisation/Streamable.h ) - - list( APPEND eckit_persist_srcs -persist/Bless.h -persist/DumpLoad.cc -persist/DumpLoad.h -persist/Exporter.cc -persist/Exporter.h -persist/Isa.cc -persist/Isa.h + persist/Bless.h + persist/DumpLoad.cc + persist/DumpLoad.h + persist/Exporter.cc + persist/Exporter.h + persist/Isa.cc + persist/Isa.h ) list( APPEND eckit_utils_srcs - utils/ByteSwap.h - utils/Compressor.cc - utils/Compressor.h - utils/Hash.cc - utils/Hash.h - utils/HyperCube.cc - utils/HyperCube.h - utils/MD5.cc - utils/MD5.h - utils/EnumBitmask.h - utils/Optional.h - utils/Overloaded.h - utils/RLE.cc - utils/RLE.h - utils/Regex.cc - utils/Regex.h - utils/RendezvousHash.cc - utils/RendezvousHash.h - utils/StringTools.cc - utils/StringTools.h - utils/Tokenizer.cc - utils/Tokenizer.h - utils/Clock.h - utils/Translator.cc - utils/Translator.h + utils/ByteSwap.h + utils/Clock.h + utils/Compressor.cc + utils/Compressor.h + utils/EnumBitmask.h + utils/Hash.cc + utils/Hash.h + utils/HyperCube.cc + utils/HyperCube.h + utils/MD5.cc + utils/MD5.h + utils/Optional.h + utils/Overloaded.h + utils/RLE.cc + utils/RLE.h + utils/Regex.cc + utils/Regex.h + utils/RendezvousHash.cc + utils/RendezvousHash.h + utils/StringTools.cc + utils/StringTools.h + utils/Tokenizer.cc + utils/Tokenizer.h + utils/Translator.cc + utils/Translator.h ) if(eckit_HAVE_BZIP2) list( APPEND eckit_utils_srcs - utils/BZip2Compressor.cc - utils/BZip2Compressor.h + utils/BZip2Compressor.cc + utils/BZip2Compressor.h ) endif() if(eckit_HAVE_SNAPPY) - list( APPEND eckit_utils_srcs - utils/SnappyCompressor.cc - utils/SnappyCompressor.h - ) + list( APPEND eckit_utils_srcs + utils/SnappyCompressor.cc + utils/SnappyCompressor.h + ) endif() if(eckit_HAVE_LZ4) - list( APPEND eckit_utils_srcs - utils/LZ4Compressor.cc - utils/LZ4Compressor.h - ) + list( APPEND eckit_utils_srcs + utils/LZ4Compressor.cc + utils/LZ4Compressor.h + ) endif() if(eckit_HAVE_AEC) - list( APPEND eckit_utils_srcs - utils/AECCompressor.cc - utils/AECCompressor.h - ) + list( APPEND eckit_utils_srcs + utils/AECCompressor.cc + utils/AECCompressor.h + ) endif() if(eckit_HAVE_SSL) list( APPEND eckit_utils_srcs - utils/MD4.cc - utils/MD4.h - utils/SHA1.cc - utils/SHA1.h + utils/MD4.cc + utils/MD4.h + utils/SHA1.cc + utils/SHA1.h ) endif() if(eckit_HAVE_RSYNC) - list( APPEND eckit_utils_srcs - utils/Rsync.cc - utils/Rsync.h - ) + list( APPEND eckit_utils_srcs + utils/Rsync.cc + utils/Rsync.h + ) endif() if(eckit_HAVE_XXHASH) @@ -789,50 +784,49 @@ endif() list( APPEND eckit_memory_srcs -memory/Builder.cc -memory/Builder.h -memory/Counted.cc -memory/Counted.h -memory/Factory.h -memory/MMap.cc -memory/MMap.h -memory/MapAllocator.cc -memory/MapAllocator.h -memory/MemoryBuffer.cc -memory/MemoryBuffer.h -memory/NonCopyable.cc -memory/NonCopyable.h -memory/OnlyMovable.h -memory/Owned.h -memory/Padded.h -memory/ScopedPtr.h -memory/SharedPtr.cc -memory/SharedPtr.h -memory/Shmget.cc -memory/Shmget.h -memory/Zero.h + memory/Builder.h + memory/Counted.cc + memory/Counted.h + memory/Factory.h + memory/MMap.cc + memory/MMap.h + memory/MapAllocator.cc + memory/MapAllocator.h + memory/MemoryBuffer.cc + memory/MemoryBuffer.h + memory/NonCopyable.cc + memory/NonCopyable.h + memory/OnlyMovable.h + memory/Owned.h + memory/Padded.h + memory/ScopedPtr.h + memory/SharedPtr.cc + memory/SharedPtr.h + memory/Shmget.cc + memory/Shmget.h + memory/Zero.h ) list( APPEND eckit_compat_srcs -compat/Inited.h -compat/StrStream.h + compat/Inited.h + compat/StrStream.h ) list( APPEND eckit_maths_srcs -maths/Functions.cc -maths/Functions.h + maths/Functions.cc + maths/Functions.h ) list( APPEND eckit_system_srcs - system/Plugin.cc - system/Plugin.h system/Library.cc system/Library.h system/LibraryManager.cc system/LibraryManager.h system/MemoryInfo.cc system/MemoryInfo.h + system/Plugin.cc + system/Plugin.h system/ResourceUsage.cc system/ResourceUsage.h system/SystemInfo.cc @@ -856,10 +850,10 @@ if(HAVE_JEMALLOC) endif() list( APPEND eckit_bases_srcs -bases/Loader.cc -bases/Loader.h -bases/Watcher.cc -bases/Watcher.h + bases/Loader.cc + bases/Loader.h + bases/Watcher.cc + bases/Watcher.h ) list( APPEND eckit_transaction_srcs @@ -901,43 +895,40 @@ list( APPEND eckit_dirs ) foreach( dir ${eckit_dirs} ) - list( APPEND eckit_srcs ${eckit_${dir}_srcs} ) + list( APPEND eckit_srcs ${eckit_${dir}_srcs} ) endforeach() list( APPEND eckit_templates - container/BTree.cc - container/BloomFilter.cc - container/CacheLRU.cc - container/MappedArray.cc - container/SharedMemArray.cc - container/Trie.cc - container/bsptree/BSPNode.cc - container/kdtree/KDNode.cc - container/sptree/SPNode.cc - filesystem/BasePathNameT.cc - io/FileBase.cc - option/FactoryOption.cc - option/SimpleOption.cc - option/VectorOption.cc - runtime/PipeHandler.cc - serialisation/Reanimator.cc - transaction/TxnLog.cc - types/Types.cc - utils/RLE.cc + container/BTree.cc + container/BloomFilter.cc + container/CacheLRU.cc + container/MappedArray.cc + container/SharedMemArray.cc + container/Trie.cc + container/bsptree/BSPNode.cc + container/kdtree/KDNode.cc + container/sptree/SPNode.cc + filesystem/BasePathNameT.cc + io/FileBase.cc + runtime/PipeHandler.cc + serialisation/Reanimator.cc + transaction/TxnLog.cc + types/Types.cc + utils/RLE.cc ) list( APPEND eckit_persistent - io/Length.h - io/Offset.h - types/ClimateDate.h - types/Date.h - types/DateTime.h - types/DayOfYear.h - types/Double.h - types/Grid.h - types/Month.h - types/Time.h - types/VerifyingDate.h + io/Length.h + io/Offset.h + types/ClimateDate.h + types/Date.h + types/DateTime.h + types/DayOfYear.h + types/Double.h + types/Grid.h + types/Month.h + types/Time.h + types/VerifyingDate.h ) ### eckit library @@ -949,13 +940,13 @@ ecbuild_add_library( HEADER_DESTINATION ${INSTALL_INCLUDE_DIR}/eckit SOURCES - ${eckit_srcs} + ${eckit_srcs} TEMPLATES - ${eckit_templates} + ${eckit_templates} PERSISTENT - ${eckit_persistent} + ${eckit_persistent} PUBLIC_INCLUDES $ @@ -996,11 +987,11 @@ ecbuild_add_library( ### sub-directories -if( HAVE_ECKIT_CMD ) +if( eckit_HAVE_ECKIT_CMD ) add_subdirectory( cmd ) endif() -if( HAVE_ECKIT_SQL ) +if( eckit_HAVE_ECKIT_SQL ) add_subdirectory( sql ) endif() @@ -1012,6 +1003,10 @@ add_subdirectory( mpi ) add_subdirectory( option ) add_subdirectory( web ) -if( HAVE_ECKIT_CODEC ) +if( eckit_HAVE_ECKIT_CODEC ) add_subdirectory( codec ) endif() + +if( eckit_HAVE_ECKIT_GEO ) + add_subdirectory( geo ) +endif() diff --git a/src/eckit/cmd/CmdParser.cc b/src/eckit/cmd/CmdParser.cc index 690efba59..9030e6adf 100644 --- a/src/eckit/cmd/CmdParser.cc +++ b/src/eckit/cmd/CmdParser.cc @@ -64,8 +64,6 @@ static History history_; // For Lex static bool eckit_cmd_debug_; -static long pos_ = 0; - //---------------------------------------------------------------------------------------------------------------------- namespace CmdYacc { @@ -94,7 +92,6 @@ void eckit_cmd_error(const char* msg) { void CmdParser::parse(const std::string& line, std::ostream& out) { command_ = line; - pos_ = 0; out_ = &out; if (command_.size() > 0) { @@ -153,7 +150,6 @@ void CmdParser::parse(std::istream& in, std::ostream& out, const Prompter& promp } command_ = p; - pos_ = 0; if (command_.size() > 0) { // Prepare for parse diff --git a/src/eckit/cmd/DirCmd.cc b/src/eckit/cmd/DirCmd.cc index 1eaf87a2b..5eff59396 100644 --- a/src/eckit/cmd/DirCmd.cc +++ b/src/eckit/cmd/DirCmd.cc @@ -8,7 +8,7 @@ * nor does it submit to any jurisdiction. */ -#include +#include #include #include "eckit/cmd/DirCmd.h" diff --git a/src/eckit/cmd/KillCmd.cc b/src/eckit/cmd/KillCmd.cc index 031e78bf0..8a277eb5a 100644 --- a/src/eckit/cmd/KillCmd.cc +++ b/src/eckit/cmd/KillCmd.cc @@ -8,7 +8,7 @@ * nor does it submit to any jurisdiction. */ -#include +#include #include #include "eckit/cmd/KillCmd.h" diff --git a/src/eckit/cmd/LibEcKitCmd.h b/src/eckit/cmd/LibEcKitCmd.h index cbd1a9e95..451965a03 100644 --- a/src/eckit/cmd/LibEcKitCmd.h +++ b/src/eckit/cmd/LibEcKitCmd.h @@ -21,9 +21,9 @@ namespace eckit { //---------------------------------------------------------------------------------------------------------------------- class LibEcKitCmd : public eckit::system::Library { + LibEcKitCmd(); public: // methods - LibEcKitCmd(); static LibEcKitCmd& instance(); diff --git a/src/eckit/cmd/StopCmd.cc b/src/eckit/cmd/StopCmd.cc index 7bbc89c90..acb1036e6 100644 --- a/src/eckit/cmd/StopCmd.cc +++ b/src/eckit/cmd/StopCmd.cc @@ -8,7 +8,7 @@ * nor does it submit to any jurisdiction. */ -#include +#include #include #include "eckit/cmd/StopCmd.h" diff --git a/src/eckit/cmd/UserInput.cc b/src/eckit/cmd/UserInput.cc index cd73dd269..d27beca44 100644 --- a/src/eckit/cmd/UserInput.cc +++ b/src/eckit/cmd/UserInput.cc @@ -1,8 +1,8 @@ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include @@ -124,8 +124,9 @@ typedef struct context { static bool processCode(int c, context* s); static void output(const context* s) { - char* buffer = (char*)malloc(strlen(s->prompt) + strlen(s->curr->edit) + 20); - sprintf(buffer, "\r%s%s\033[0K\r\033[%luC", s->prompt, s->curr->edit, strlen(s->prompt) + s->pos); + const size_t numBytes = strlen(s->prompt) + strlen(s->curr->edit) + 20; + char* buffer = (char*)malloc(numBytes); + snprintf(buffer, numBytes,"\r%s%s\033[0K\r\033[%luC", s->prompt, s->curr->edit, strlen(s->prompt) + s->pos); write(1, buffer, strlen(buffer)); free(buffer); } @@ -285,10 +286,7 @@ static bool processCode(int c, context* s) { if (strlen(s->curr->edit)) { return processCode(BACKSPACE, s); } - else { - return processCode(0, s); - } - break; + return processCode(0, s); case BACKSPACE: case CONTROL_H: @@ -420,14 +418,12 @@ static bool processCode(int c, context* s) { case CR: write(1, "\r\n", 2); return true; - break; case CONTROL_C: write(1, "\r\n", 2); s->pos = 0; return processCode(0, s); // CONTROL_C behaves as CONTROL_D -- for backward compatibility to previous marsadm - break; case CONTROL_G: break; diff --git a/src/eckit/codec/CMakeLists.txt b/src/eckit/codec/CMakeLists.txt index 4875895c2..46ec94676 100644 --- a/src/eckit/codec/CMakeLists.txt +++ b/src/eckit/codec/CMakeLists.txt @@ -2,7 +2,7 @@ configure_file( eckit_codec_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/eckit_codec_config.h @ONLY ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/eckit_codec_config.h - DESTINATION ${INSTALL_INCLUDE_DIR}/eckit + DESTINATION ${INSTALL_INCLUDE_DIR}/eckit/codec ) ecbuild_add_library( diff --git a/src/eckit/codec/Metadata.h b/src/eckit/codec/Metadata.h index 4bd290608..c8e600339 100644 --- a/src/eckit/codec/Metadata.h +++ b/src/eckit/codec/Metadata.h @@ -57,14 +57,8 @@ class Metadata : public LocalConfiguration { // extended LocalConfiguration: using LocalConfiguration::set; - Metadata& set(const LocalConfiguration& other) { - auto& root = const_cast(get()); - const auto& other_root = other.get(); - std::vector other_keys; - fromValue(other_keys, other_root.keys()); - for (auto& key : other_keys) { - root[key] = other_root[key]; - } + Metadata& set(const Configuration& other) { + LocalConfiguration::set(other); return *this; } @@ -79,18 +73,9 @@ class Metadata : public LocalConfiguration { Metadata& remove(const std::string& name) { - auto& root = const_cast(get()); - root.remove(name); + LocalConfiguration::remove(name); return *this; } - - - std::vector keys() const { - // Preserves order of keys - std::vector result; - fromValue(result, get().keys()); - return result; - } }; //--------------------------------------------------------------------------------------------------------------------- diff --git a/src/eckit/codec/Stream.cc b/src/eckit/codec/Stream.cc index 96bdbc5c4..5a49f4d4f 100644 --- a/src/eckit/codec/Stream.cc +++ b/src/eckit/codec/Stream.cc @@ -25,8 +25,6 @@ Stream::Stream(DataHandle* datahandle) : shared_(datahandle), ptr_(shared_.get() Stream::Stream(std::shared_ptr datahandle) : shared_(datahandle), ptr_(shared_.get()) {} -Stream::Stream(const Stream& other) = default; - DataHandle& Stream::datahandle() { ASSERT(ptr_ != nullptr); return *ptr_; diff --git a/src/eckit/codec/Stream.h b/src/eckit/codec/Stream.h index 3c5ecc258..8f2fe3c9d 100644 --- a/src/eckit/codec/Stream.h +++ b/src/eckit/codec/Stream.h @@ -46,8 +46,11 @@ class Stream { /// the referenced datahandle Stream(DataHandle&); - /// Assignment constructor sharing datahandle with other Stream - Stream(const Stream&); + /// Assignment/Copy constructor sharing datahandle with other Stream + Stream(const Stream&) = default; + Stream(Stream&&) = default; + Stream& operator=(const Stream&) = default; + Stream& operator=(Stream&&) = default; /// Access internal DataHandle DataHandle& datahandle(); diff --git a/src/eckit/codec/detail/demangle.h b/src/eckit/codec/detail/demangle.h index 9e7c0f2be..f6bb67eb9 100644 --- a/src/eckit/codec/detail/demangle.h +++ b/src/eckit/codec/detail/demangle.h @@ -13,6 +13,7 @@ #pragma once #include +#include namespace eckit::codec { diff --git a/src/eckit/codec/types/array/ArrayMetadata.h b/src/eckit/codec/types/array/ArrayMetadata.h index 07b49a0d5..f5e642173 100644 --- a/src/eckit/codec/types/array/ArrayMetadata.h +++ b/src/eckit/codec/types/array/ArrayMetadata.h @@ -84,8 +84,8 @@ class ArrayShape : public std::vector { class ArrayMetadata { public: - using ArrayShape = ArrayShape; - using DataType = DataType; + using ArrayShape = eckit::codec::ArrayShape; + using DataType = eckit::codec::DataType; static std::string type() { return "array"; } diff --git a/src/eckit/codec/types/scalar.cc b/src/eckit/codec/types/scalar.cc index e67966cf9..2729e91d7 100644 --- a/src/eckit/codec/types/scalar.cc +++ b/src/eckit/codec/types/scalar.cc @@ -16,7 +16,7 @@ #endif // GNU C++ compiler (version 11) should not try to optimize this code -#if defined(__GNUC__) && !defined(__NVCOMPILER) +#if defined(__GNUC__) && !defined(__NVCOMPILER) && !defined(__clang__) #pragma GCC optimize("O0") #endif diff --git a/src/eckit/config/Configuration.cc b/src/eckit/config/Configuration.cc index 4e47d3b03..b06655090 100644 --- a/src/eckit/config/Configuration.cc +++ b/src/eckit/config/Configuration.cc @@ -73,6 +73,10 @@ eckit::Value Configuration::lookUp(const std::string& s, bool& found) const { eckit::Value result = *root_; for (size_t i = 0; i < path.size(); i++) { + if (!(result.isMap() || result.isOrderedMap())) { + found = false; + return result; + } const std::string& key = path[i]; if (!result.contains(key)) { found = false; @@ -299,7 +303,7 @@ bool Configuration::get(const std::string& name, LocalConfiguration& value) cons return found; } -const Value& Configuration::get() const { +const Value& Configuration::getValue() const { return *root_; } @@ -460,6 +464,113 @@ LocalConfiguration Configuration::getSubConfiguration(const std::string& name) c return result; } +bool Configuration::isIntegral(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + return found && v.isNumber(); +} + +bool Configuration::isBoolean(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + return found && v.isBool(); +} + +bool Configuration::isFloatingPoint(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + return found && v.isDouble(); +} + +bool Configuration::isString(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + return found && v.isString(); +} + +bool Configuration::isList(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + return found && v.isList(); +} + +bool Configuration::isSubConfiguration(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + return found && (v.isMap() || v.isOrderedMap()); +} + +bool Configuration::isNull(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + return found && (v.isNil()); +} + +bool Configuration::isIntegralList(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + if (found && v.isList()) { + if (v.size() == 0) { + return true; + } + auto& firstElement = v[0]; + return firstElement.isNumber(); + } + return false; +} + +bool Configuration::isBooleanList(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + if (found && v.isList()) { + if (v.size() == 0) { + return true; + } + auto& firstElement = v[0]; + return firstElement.isBool(); + } + return false; +} + +bool Configuration::isFloatingPointList(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + if (found && v.isList()) { + if (v.size() == 0) { + return true; + } + auto& firstElement = v[0]; + return firstElement.isDouble(); + } + return false; +} + +bool Configuration::isStringList(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + if (found && v.isList()) { + if (v.size() == 0) { + return true; + } + auto& firstElement = v[0]; + return firstElement.isString(); + } + return false; +} + +bool Configuration::isSubConfigurationList(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + if (found && v.isList()) { + if (v.size() == 0) { + return true; + } + auto& firstElement = v[0]; + return firstElement.isMap() || firstElement.isOrderedMap(); + } + return false; +} + template void Configuration::_getWithDefault(const std::string& name, T& value, const T& defaultVal) const { if (!get(name, value)) { @@ -581,10 +692,7 @@ void Configuration::json(JSON& s) const { std::vector Configuration::keys() const { std::vector result; - ValueMap m = *root_; - for (ValueMap::const_iterator j = m.begin(); j != m.end(); ++j) { - result.push_back((*j).first); - } + eckit::fromValue(result, root_->keys()); return result; } diff --git a/src/eckit/config/Configuration.h b/src/eckit/config/Configuration.h index 8f08f7802..c4391e820 100644 --- a/src/eckit/config/Configuration.h +++ b/src/eckit/config/Configuration.h @@ -18,6 +18,7 @@ #include #include +#include #include "eckit/config/Parametrisation.h" @@ -125,11 +126,80 @@ class Configuration : public Parametrisation { bool get(const std::string& name, LocalConfiguration&) const; /// @todo This method should be protected. As per note above, - /// we don't wnat to expose eckit::Value out of Configuration. - const Value& get() const; + /// we don't want to expose eckit::Value out of Configuration. + [[deprecated("eckit::Value should not be exposed via eckit::Configuration::get(). This method Will be removed in a next release.")]] + const Value& get() const { + return getValue(); + } virtual void hash(eckit::Hash&) const; + // -- Introspection methods + + bool isSubConfiguration(const std::string& name) const; + + bool isIntegral(const std::string& name) const; + + bool isBoolean(const std::string& name) const; + + bool isFloatingPoint(const std::string& name) const; + + bool isString(const std::string& name) const; + + bool isList(const std::string& name) const; + + bool isSubConfigurationList(const std::string& name) const; + + bool isIntegralList(const std::string& name) const; + + bool isBooleanList(const std::string& name) const; + + bool isFloatingPointList(const std::string& name) const; + + bool isStringList(const std::string& name) const; + + bool isNull(const std::string& name) const; + + template + bool isConvertible(const std::string& name) const { + using _T = std::decay_t; + if constexpr(std::is_base_of_v) { + return isSubConfiguration(name); + } + else if constexpr(std::is_same_v<_T,int> || std::is_same_v<_T,long> || std::is_same_v<_T,long long> || std::is_same_v<_T,std::size_t>) { + return isIntegral(name) || isBoolean(name); + } + else if constexpr(std::is_same_v<_T,float> || std::is_same_v<_T,double>) { + return isFloatingPoint(name) || isIntegral(name) || isBoolean(name); + } + else if constexpr(std::is_same_v<_T,std::string>) { + return isString(name); + } + else if constexpr(is_vector<_T>::value) { + using _V = std::decay_t; + if constexpr(std::is_base_of_v) { + return isSubConfigurationList(name); + } + else if constexpr(std::is_same_v<_V,int> || std::is_same_v<_V,long> || std::is_same_v<_V,long long> || std::is_same_v<_V,std::size_t>) { + return isIntegralList(name) || isBooleanList(name); + } + else if constexpr(std::is_same_v<_V,float> || std::is_same_v<_V,double>) { + return isFloatingPointList(name) || isIntegralList(name) || isBooleanList(name); + } + else if constexpr(std::is_same_v<_V,std::string>) { + return isStringList(name); + } + } + else { + return false; + } + } + + template + bool isConvertible(const std::string& name, T&) const { + return isConvertible(name); + } + protected: // methods Configuration(const eckit::Value&, char separator = '.'); @@ -144,7 +214,10 @@ class Configuration : public Parametrisation { operator Value() const; + const Value& getValue() const; + protected: // members + friend class LocalConfiguration; std::unique_ptr root_; char separator_; @@ -167,6 +240,21 @@ class Configuration : public Parametrisation { p.print(s); return s; } + +private: + + // Helper structs for introspection of template T in isConvertible method + template + struct is_vector { + using type = T ; + constexpr static bool value = false; + }; + + template + struct is_vector> { + using type = std::vector ; + constexpr static bool value = true; + }; }; //---------------------------------------------------------------------------------------------------------------------- diff --git a/src/eckit/config/LibEcKit.cc b/src/eckit/config/LibEcKit.cc index d7f9610ab..5fdef6365 100644 --- a/src/eckit/config/LibEcKit.cc +++ b/src/eckit/config/LibEcKit.cc @@ -12,7 +12,7 @@ /// @author Tiago Quintino /// @date August 2016 -#include +#include #include #include diff --git a/src/eckit/config/LocalConfiguration.cc b/src/eckit/config/LocalConfiguration.cc index 863fdfce8..d85cb5e01 100644 --- a/src/eckit/config/LocalConfiguration.cc +++ b/src/eckit/config/LocalConfiguration.cc @@ -185,20 +185,34 @@ LocalConfiguration& LocalConfiguration::set(const std::string& s, const std::vec } -LocalConfiguration& LocalConfiguration::set(const std::string& s, const LocalConfiguration& value) { - setValue(s, *value.root_); +LocalConfiguration& LocalConfiguration::set(const std::string& s, const Configuration& value) { + setValue(s, value.getValue()); return *this; } -LocalConfiguration& LocalConfiguration::set(const std::string& s, const std::vector& value) { +LocalConfiguration& LocalConfiguration::set(const std::string& s, const Configuration* value[], size_t size) { ValueList values; - for (std::vector::const_iterator v = value.begin(); v != value.end(); ++v) { - values.push_back(*v->root_); + for (size_t i=0; igetValue()); } setValue(s, values); return *this; } +LocalConfiguration& LocalConfiguration::set(const Configuration& other) { + eckit::Value& root = *root_; + eckit::Value const& other_root = *other.root_; + for (auto& key : other.keys()) { + root[key] = other_root[key]; + } + return *this; +} + +LocalConfiguration& LocalConfiguration::remove(const std::string& name) { + root_->remove(name); + return *this; +} + //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit diff --git a/src/eckit/config/LocalConfiguration.h b/src/eckit/config/LocalConfiguration.h index b0dec450d..5ae3f4af7 100644 --- a/src/eckit/config/LocalConfiguration.h +++ b/src/eckit/config/LocalConfiguration.h @@ -15,7 +15,9 @@ #ifndef eckit_LocalConfiguration_H #define eckit_LocalConfiguration_H +#include #include +#include #include "eckit/config/Configuration.h" #include "eckit/config/Configured.h" @@ -42,6 +44,8 @@ class LocalConfiguration : public Configuration, public Configured { ~LocalConfiguration() override; + LocalConfiguration& set(const Configuration& other); + LocalConfiguration& set(const std::string& name, const std::string& value) override; LocalConfiguration& set(const std::string& name, const char* value) override; LocalConfiguration& set(const std::string& name, bool value) override; @@ -60,8 +64,32 @@ class LocalConfiguration : public Configuration, public Configured { LocalConfiguration& set(const std::string& name, const std::vector& value) override; LocalConfiguration& set(const std::string& name, const std::vector& value) override; - LocalConfiguration& set(const std::string& name, const LocalConfiguration& value); - LocalConfiguration& set(const std::string& name, const std::vector& value); + LocalConfiguration& set(const std::string& name, const Configuration& value); + + template >>> + LocalConfiguration& set(const std::string& name, const std::vector& value) { + std::vector abstract_pointers; + abstract_pointers.reserve(value.size()); + for (auto& v: value) { + abstract_pointers.emplace_back(&v); + } + return set(name, abstract_pointers.data(), abstract_pointers.size()); + } + + template >>> + LocalConfiguration& set(const std::string& name, std::initializer_list&& value) { + std::vector abstract_pointers; + abstract_pointers.reserve(value.size()); + for (auto& v: value) { + abstract_pointers.emplace_back(&v); + } + return set(name, abstract_pointers.data(), abstract_pointers.size()); + } + + + LocalConfiguration& remove(const std::string& name); protected: friend class Configuration; @@ -71,6 +99,8 @@ class LocalConfiguration : public Configuration, public Configured { void print(std::ostream&) const override; + LocalConfiguration& set(const std::string& name, const Configuration* abstract_pointers[], size_t size); + private: void setValue(const std::vector& path, size_t i, Value& root, const Value& value); void setValue(const std::string& s, const Value& value); diff --git a/src/eckit/config/ResourceMgr.cc b/src/eckit/config/ResourceMgr.cc index db4c1c51e..eb4e6b798 100644 --- a/src/eckit/config/ResourceMgr.cc +++ b/src/eckit/config/ResourceMgr.cc @@ -199,8 +199,8 @@ int ResourceQualifier::operator<(const ResourceQualifier& other) const { char buf1[1024]; char buf2[1024]; - sprintf(buf1, "%s.%s.%s", owner_.c_str(), kind_.c_str(), name_.c_str()); - sprintf(buf2, "%s.%s.%s", other.owner_.c_str(), other.kind_.c_str(), other.name_.c_str()); + snprintf(buf1, sizeof(buf1), "%s.%s.%s", owner_.c_str(), kind_.c_str(), name_.c_str()); + snprintf(buf2, sizeof(buf2), "%s.%s.%s", other.owner_.c_str(), other.kind_.c_str(), other.name_.c_str()); return strcmp(buf1, buf2) < 0; } diff --git a/src/eckit/container/Cache.h b/src/eckit/container/Cache.h index f8ea84143..f6ae3a2f0 100644 --- a/src/eckit/container/Cache.h +++ b/src/eckit/container/Cache.h @@ -14,7 +14,7 @@ #ifndef eckit_container_Cache_h #define eckit_container_Cache_h -#include +#include #include #include diff --git a/src/eckit/container/MappedArray.cc b/src/eckit/container/MappedArray.cc index f54c03b99..531c09a3a 100644 --- a/src/eckit/container/MappedArray.cc +++ b/src/eckit/container/MappedArray.cc @@ -11,7 +11,7 @@ #include "eckit/eckit.h" #include -#include +#include #include #include #include diff --git a/src/eckit/container/MappedArray.h b/src/eckit/container/MappedArray.h index 8f9effd5c..07e4f127f 100644 --- a/src/eckit/container/MappedArray.h +++ b/src/eckit/container/MappedArray.h @@ -14,7 +14,7 @@ #ifndef eckit_MappedArray_h #define eckit_MappedArray_h -#include +#include #include "eckit/exception/Exceptions.h" #include "eckit/filesystem/PathName.h" diff --git a/src/eckit/container/SharedMemArray.cc b/src/eckit/container/SharedMemArray.cc index a08a6d24f..bc951429d 100644 --- a/src/eckit/container/SharedMemArray.cc +++ b/src/eckit/container/SharedMemArray.cc @@ -9,8 +9,8 @@ */ #include -#include -#include +#include +#include #include #include diff --git a/src/eckit/container/SharedMemArray.h b/src/eckit/container/SharedMemArray.h index 2e6ffc662..db5951d65 100644 --- a/src/eckit/container/SharedMemArray.h +++ b/src/eckit/container/SharedMemArray.h @@ -15,7 +15,7 @@ #ifndef eckit_SharedMemArray_h #define eckit_SharedMemArray_h -#include +#include #include "eckit/memory/NonCopyable.h" #include "eckit/os/Semaphore.h" diff --git a/src/eckit/container/bsptree/BSPNode.cc b/src/eckit/container/bsptree/BSPNode.cc index edf6c7c4f..cbcc68c85 100644 --- a/src/eckit/container/bsptree/BSPNode.cc +++ b/src/eckit/container/bsptree/BSPNode.cc @@ -14,7 +14,7 @@ #include #include "eckit/eckit.h" -#include +#include #include #include diff --git a/src/eckit/eckit_config.h.in b/src/eckit/eckit_config.h.in index 18c4bfab8..0f92d7f7c 100644 --- a/src/eckit/eckit_config.h.in +++ b/src/eckit/eckit_config.h.in @@ -36,6 +36,11 @@ #cmakedefine01 eckit_HAVE_UNICODE #cmakedefine01 eckit_HAVE_XXHASH +// memory + +#cmakedefine01 eckit_HAVE_ECKIT_MEMORY_FACTORY_BUILDERS_DEBUG +#cmakedefine01 eckit_HAVE_ECKIT_MEMORY_FACTORY_EMPTY_DESTRUCTION + // external packages #cmakedefine01 eckit_HAVE_ARMADILLO diff --git a/src/eckit/exception/Exceptions.cc b/src/eckit/exception/Exceptions.cc index 431d9896c..10014ef9e 100644 --- a/src/eckit/exception/Exceptions.cc +++ b/src/eckit/exception/Exceptions.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include #include "eckit/config/LibEcKit.h" diff --git a/src/eckit/exception/Exceptions.h b/src/eckit/exception/Exceptions.h index a2f6acd0d..db854e95c 100644 --- a/src/eckit/exception/Exceptions.h +++ b/src/eckit/exception/Exceptions.h @@ -11,7 +11,7 @@ #ifndef eckit_Exceptions_h #define eckit_Exceptions_h -#include +#include #include #include @@ -186,7 +186,7 @@ class OutOfRange : public Exception { }; /// For compatibility -using MethodNotYetImplemented = NotImplemented; +using MethodNotYetImplemented [[deprecated("Use eckit::NotImplemented directly")]] = NotImplemented; class FunctionalityNotSupported : public NotImplemented { public: diff --git a/src/eckit/filesystem/LocalPathName.cc b/src/eckit/filesystem/LocalPathName.cc index 980e62872..0e447123a 100644 --- a/src/eckit/filesystem/LocalPathName.cc +++ b/src/eckit/filesystem/LocalPathName.cc @@ -11,9 +11,9 @@ #include "LocalPathName.h" #include -#include +#include #include -#include +#include #include #include #include @@ -29,6 +29,7 @@ #include "eckit/config/LibEcKit.h" #include "eckit/config/Resource.h" #include "eckit/filesystem/BasePathNameT.h" +#include "eckit/filesystem/PathName.h" #include "eckit/filesystem/PathNameFactory.h" #include "eckit/filesystem/StdDir.h" #include "eckit/io/FileHandle.h" @@ -149,6 +150,9 @@ static void init() { //---------------------------------------------------------------------------------------------------------------------- +LocalPathName::LocalPathName(const PathName& path) : + LocalPathName(path.path(), false, true) {} + LocalPathName LocalPathName::baseName(bool ext) const { const char* q = path_.c_str(); int n = -1; @@ -415,6 +419,18 @@ void operator>>(Stream& s, LocalPathName& path) { s >> path.path_; } +LocalPathName operator/(const LocalPathName& p1, const LocalPathName& p2) { + bool tildeIsUserHome = false; + bool skipTildeExpansion = true; + return eckit::LocalPathName(p1.path() + "/" + p2.path(), tildeIsUserHome, skipTildeExpansion); +} + +LocalPathName operator/(const LocalPathName& p1, const char* p2) { + bool tildeIsUserHome = false; + bool skipTildeExpansion = true; + return eckit::LocalPathName(p1.path() + "/" + p2, tildeIsUserHome, skipTildeExpansion); +} + LocalPathName LocalPathName::fullName() const { if (path_.length() > 0 && path_[0] != '/') { char buf[PATH_MAX]; diff --git a/src/eckit/filesystem/LocalPathName.h b/src/eckit/filesystem/LocalPathName.h index 4cd9b61a6..079a2b4a0 100644 --- a/src/eckit/filesystem/LocalPathName.h +++ b/src/eckit/filesystem/LocalPathName.h @@ -29,6 +29,7 @@ namespace eckit { class Length; class DataHandle; class BasePathName; +class PathName; struct FileSystemSize; @@ -54,6 +55,7 @@ class LocalPathName { tidy(tildeIsUserHome, skipTildeExpansion); } } + explicit LocalPathName(const PathName& path); LocalPathName(const LocalPathName& p) : path_(p.path_) {} @@ -279,6 +281,10 @@ class LocalPathName { friend LocalPathName operator+(const LocalPathName& p, const char* s) { return LocalPathName(p.path_ + s); } friend LocalPathName operator+(const LocalPathName& p, char s) { return LocalPathName(p.path_ + s); } + + friend LocalPathName operator/(const LocalPathName& p1, const LocalPathName& p2); + + friend LocalPathName operator/(const LocalPathName& p1, const char* p2); }; template <> diff --git a/src/eckit/filesystem/PathName.cc b/src/eckit/filesystem/PathName.cc index fdd3d1fd3..52f702e0d 100644 --- a/src/eckit/filesystem/PathName.cc +++ b/src/eckit/filesystem/PathName.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include diff --git a/src/eckit/filesystem/StdDir.cc b/src/eckit/filesystem/StdDir.cc index 01c938488..ecff78736 100644 --- a/src/eckit/filesystem/StdDir.cc +++ b/src/eckit/filesystem/StdDir.cc @@ -46,6 +46,14 @@ struct dirent* StdDir::dirent() { /// however readdir() isn't thread-safe yet, not even in glic implementation /// until then we prefer to use readdir_r #if eckit_HAVE_READDIR_R +// Disable deprecation warning of using readdir_r +#if defined(__INTEL_COMPILER) +#pragma warning ( disable:1478 ) +#elif defined(__NVCOMPILER) +#pragma diag_suppress 1216 +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif ::readdir_r(d_, &buf, &e); #else e = ::readdir(d_); diff --git a/src/eckit/filesystem/TmpFile.cc b/src/eckit/filesystem/TmpFile.cc index e02f436d6..be7af0103 100644 --- a/src/eckit/filesystem/TmpFile.cc +++ b/src/eckit/filesystem/TmpFile.cc @@ -26,7 +26,7 @@ static PathName tmp() { long max = pathconf(tmpdir, _PC_PATH_MAX); char* path = new char[max]; - sprintf(path, "%s/eckitXXXXXXXXXXX", tmpdir); + snprintf(path, max, "%s/eckitXXXXXXXXXXX", tmpdir); int fd; SYSCALL2(fd = ::mkstemp(path), path); diff --git a/src/eckit/filesystem/URI.h b/src/eckit/filesystem/URI.h index 0cd9a4824..523d3e3f8 100644 --- a/src/eckit/filesystem/URI.h +++ b/src/eckit/filesystem/URI.h @@ -43,7 +43,7 @@ class URI { // Contructors URI(); - URI(const std::string& uri); + explicit URI(const std::string& uri); URI(const std::string& scheme, const PathName& path); URI(const std::string& scheme, const URI& uri); URI(const std::string& scheme, const std::string& hostname, int port); diff --git a/src/eckit/geo/Area.cc b/src/eckit/geo/Area.cc new file mode 100644 index 000000000..1f11164a1 --- /dev/null +++ b/src/eckit/geo/Area.cc @@ -0,0 +1,39 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Area.h" + +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo { + + +spec::Custom* Area::spec() const { + auto* custom = new spec::Custom; + ASSERT(custom != nullptr); + + fill_spec(*custom); + return custom; +} + + +std::string Area::spec_str() const { + std::unique_ptr custom(spec()); + return custom->str(); +} + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Area.h b/src/eckit/geo/Area.h new file mode 100644 index 000000000..7335d492a --- /dev/null +++ b/src/eckit/geo/Area.h @@ -0,0 +1,86 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + +#include "eckit/memory/Builder.h" +#include "eckit/memory/Factory.h" + + +namespace eckit::geo { +namespace area { +class BoundingBox; +} +class Spec; +namespace spec { +class Custom; +} +} // namespace eckit::geo + + +namespace eckit::geo { + + +class Area { +public: + // -- Types + + using builder_t = BuilderT1; + using ARG1 = const Spec&; + + // -- Constructors + + Area() noexcept = default; + + Area(const Area&) = default; + Area(Area&&) = default; + + // -- Destructor + + virtual ~Area() = default; + + // -- Operators + + Area& operator=(const Area&) = default; + Area& operator=(Area&&) = default; + + // -- Methods + + [[nodiscard]] spec::Custom* spec() const; + std::string spec_str() const; + + virtual bool intersects(area::BoundingBox&) const = 0; + + // -- Class methods + + static std::string className() { return "area"; } + +private: + // -- Methods + + virtual void fill_spec(spec::Custom&) const = 0; + + // -- Friends + + friend class Grid; +}; + + +// using AreaFactory = Factory; + +// template +// using AreaBuilder = ConcreteBuilderT1; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/CMakeLists.txt b/src/eckit/geo/CMakeLists.txt new file mode 100644 index 000000000..bea069047 --- /dev/null +++ b/src/eckit/geo/CMakeLists.txt @@ -0,0 +1,186 @@ + +list(APPEND eckit_geo_srcs + Area.cc + Area.h + Cache.cc + Cache.h + Figure.cc + Figure.h + GreatCircle.cc + GreatCircle.h + Grid.cc + Grid.h + Increments.cc + Increments.h + Iterator.cc + Iterator.h + LibEcKitGeo.cc + LibEcKitGeo.h + Ordering.cc + Ordering.h + Point.cc + Point.h + Point2.cc + Point2.h + Point3.cc + Point3.h + PointLonLat.cc + PointLonLat.h + PointLonLatR.cc + PointLonLatR.h + Projection.cc + Projection.h + Range.cc + Range.h + Renumber.h + Search.h + Shape.cc + Shape.h + Spec.cc + Spec.h + area/BoundingBox.cc + area/BoundingBox.h + etc/Grid.cc + etc/Grid.h + figure/Earth.cc + figure/Earth.h + figure/OblateSpheroid.cc + figure/OblateSpheroid.h + figure/Sphere.cc + figure/Sphere.h + figure/UnitSphere.cc + figure/UnitSphere.h + geometry/OblateSpheroid.cc + geometry/OblateSpheroid.h + geometry/Sphere.cc + geometry/Sphere.h + geometry/SphereT.h + geometry/UnitSphere.h + grid/HEALPix.cc + grid/HEALPix.h + grid/Reduced.cc + grid/Reduced.h + grid/ReducedGaussian.cc + grid/ReducedGaussian.h + grid/ReducedLL.cc + grid/ReducedLL.h + grid/Regular.cc + grid/Regular.h + grid/RegularGaussian.cc + grid/RegularGaussian.h + grid/RegularLL.cc + grid/RegularLL.h + grid/RegularXY.cc + grid/RegularXY.h + grid/Unstructured.cc + grid/Unstructured.h + grid/regular-xy/LambertAzimuthalEqualArea.cc + grid/regular-xy/LambertAzimuthalEqualArea.h + grid/regular-xy/LambertConformalConic.cc + grid/regular-xy/LambertConformalConic.h + grid/regular-xy/Mercator.cc + grid/regular-xy/Mercator.h + grid/regular-xy/PolarStereographic.cc + grid/regular-xy/PolarStereographic.h + grid/regular-xy/SpaceView.cc + grid/regular-xy/SpaceView.h + iterator/Reduced.cc + iterator/Reduced.h + iterator/Regular.cc + iterator/Regular.h + iterator/Unstructured.cc + iterator/Unstructured.h + polygon/LonLatPolygon.cc + polygon/LonLatPolygon.h + polygon/Polygon.cc + polygon/Polygon.h + projection/Composer.cc + projection/Composer.h + projection/LambertAzimuthalEqualArea.cc + projection/LambertAzimuthalEqualArea.h + projection/LambertConformalConic.cc + projection/LambertConformalConic.h + projection/LonLatToXYZ.cc + projection/LonLatToXYZ.h + projection/Mercator.cc + projection/Mercator.h + projection/None.cc + projection/None.h + projection/PolarStereographic.cc + projection/PolarStereographic.h + projection/ProjectionOnFigure.cc + projection/ProjectionOnFigure.h + projection/Reverse.h + projection/Rotation.cc + projection/Rotation.h + projection/SpaceView.cc + projection/SpaceView.h + projection/Stretch.cc + projection/Stretch.h + projection/XYToLonLat.cc + projection/XYToLonLat.h + range/GaussianLatitude.cc + range/GaussianLatitude.h + range/Regular.cc + range/Regular.h + range/RegularCartesian.cc + range/RegularCartesian.h + range/RegularLatitude.cc + range/RegularLatitude.h + range/RegularLongitude.cc + range/RegularLongitude.h + spec/Custom.cc + spec/Custom.h + spec/Generator.h + spec/Layered.cc + spec/Layered.h + util.cc + util.h + util/arange.cc + util/bounding_box.cc + util/gaussian_latitudes.cc + util/linspace.cc + util/monotonic_crop.cc + util/mutex.h + util/reduced_classical_pl.cc + util/reduced_octahedral_pl.cc + util/sincos.h +) + +set(eckit_geo_include_dirs + $ + $ +) + +set(eckit_geo_libs eckit_maths) +set(eckit_GEO_ETC_GRID "~eckit/etc/eckit/geo/grid.yaml") + +if(eckit_HAVE_PROJ) + list(APPEND eckit_geo_srcs projection/PROJ.cc projection/PROJ.h) + list(APPEND eckit_geo_libs PROJ::proj) +endif() + +if(eckit_HAVE_GEO_GRID_ORCA) + list(APPEND eckit_geo_srcs grid/ORCA.cc grid/ORCA.h) + list(APPEND eckit_geo_libs eckit_codec) + list(APPEND eckit_GEO_ETC_GRID "~eckit/etc/eckit/geo/ORCA.yaml") +endif() + +string(REPLACE ";" ":" eckit_GEO_ETC_GRID "${eckit_GEO_ETC_GRID}") +configure_file(eckit_geo_config.h.in eckit_geo_config.h @ONLY) + +list(APPEND eckit_geo_srcs ${CMAKE_CURRENT_BINARY_DIR}/eckit_geo_config.h) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/eckit_geo_config.h + DESTINATION ${INSTALL_INCLUDE_DIR}/eckit +) + +ecbuild_add_library( + TARGET eckit_geo + TYPE SHARED + INSTALL_HEADERS ALL + HEADER_DESTINATION ${INSTALL_INCLUDE_DIR}/eckit/geo + PUBLIC_LIBS ${eckit_geo_libs} + PUBLIC_INCLUDES ${eckit_geo_include_dirs} + SOURCES ${eckit_geo_srcs} +) diff --git a/src/eckit/geo/Cache.cc b/src/eckit/geo/Cache.cc new file mode 100644 index 000000000..44272d1b1 --- /dev/null +++ b/src/eckit/geo/Cache.cc @@ -0,0 +1,45 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Cache.h" + +#include +#include + + +namespace eckit::geo { + + +static util::recursive_mutex MUTEX; +static std::vector CACHES; + + +Cache::bytes_size_t Cache::total_footprint() { + util::lock_guard lock(MUTEX); + return std::accumulate(CACHES.begin(), CACHES.end(), static_cast(0), + [](bytes_size_t sum, const auto* cache) { return sum + cache->footprint(); }); +} + + +void Cache::total_purge() { + util::lock_guard lock(MUTEX); + std::for_each(CACHES.begin(), CACHES.end(), [](auto* cache) { cache->purge(); }); +} + + +Cache::Cache() { + util::lock_guard lock(MUTEX); + CACHES.emplace_back(this); +} + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Cache.h b/src/eckit/geo/Cache.h new file mode 100644 index 000000000..165edc597 --- /dev/null +++ b/src/eckit/geo/Cache.h @@ -0,0 +1,98 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/util/mutex.h" + + +namespace eckit::geo { + + +class Cache { +public: + using bytes_size_t = decltype(sizeof(int)); + + static bytes_size_t total_footprint(); + static void total_purge(); + +protected: + Cache(); + +private: + virtual bytes_size_t footprint() const = 0; + virtual void purge() = 0; +}; + + +template +class CacheT final : private Cache { +private: + template + using footprint_t = decltype(std::declval().footprint()); + + template > + struct has_footprint : std::false_type {}; + + template + struct has_footprint>> : std::true_type {}; + + template + static inline constexpr bool has_footprint_v = has_footprint::value; + +public: + using key_type = Key; + using value_type = Value; + + CacheT() : mutex_(new util::recursive_mutex) { ASSERT(mutex_ != nullptr); } + + bool contains(const key_type& key) const { + util::lock_guard lock(*mutex_); + return container_.find(key) != container_.end(); + } + + const value_type& operator[](const key_type& key) const { + util::lock_guard lock(*mutex_); + return container_[key]; + } + + value_type& operator[](const key_type& key) { + util::lock_guard lock(*mutex_); + return container_[key]; + } + + bytes_size_t footprint() const final { + util::lock_guard lock(*mutex_); + return std::accumulate(container_.begin(), container_.end(), 0, [](bytes_size_t sum, const auto& kv) { + if constexpr (has_footprint_v) { + return sum + kv.second.footprint(); + } + else { + return sum + kv.second.size() * sizeof(typename value_type::value_type); + } + }); + } + + void purge() final { container_.clear(); } + +private: + mutable std::map container_; + + util::recursive_mutex* mutex_; +}; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Figure.cc b/src/eckit/geo/Figure.cc new file mode 100644 index 000000000..d9d276ab7 --- /dev/null +++ b/src/eckit/geo/Figure.cc @@ -0,0 +1,135 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Figure.h" + +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/figure/Earth.h" +#include "eckit/geo/figure/OblateSpheroid.h" +#include "eckit/geo/figure/Sphere.h" +#include "eckit/geo/geometry/OblateSpheroid.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/geo/util/mutex.h" +#include "eckit/parser/YAMLParser.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo { + + +static util::recursive_mutex MUTEX; + + +class lock_type { + util::lock_guard lock_guard_{MUTEX}; +}; + + +double Figure::R() const { + NOTIMP; +} + + +double Figure::a() const { + NOTIMP; +} + + +double Figure::b() const { + NOTIMP; +} + + +spec::Custom* Figure::spec() const { + auto* custom = new spec::Custom; + ASSERT(custom != nullptr); + + fill_spec(*custom); + return custom; +} + + +std::string Figure::spec_str() const { + std::unique_ptr custom(spec()); + return custom->str(); +} + + +double Figure::eccentricity() const { + return geometry::OblateSpheroid::eccentricity(a(), b()); +} + + +double Figure::flattening() const { + return geometry::OblateSpheroid::flattening(a(), b()); +} + + +void Figure::fill_spec(spec::Custom& custom) const { + static const std::map, std::string> KNOWN{ + {std::shared_ptr
{new figure::GRS80}, "grs80"}, + {std::shared_ptr
{new figure::WGS84}, "wgs84"}, + }; + + for (const auto& [figure, name] : KNOWN) { + if (types::is_approximately_equal(figure->a(), a()) && types::is_approximately_equal(figure->b(), b())) { + custom.set("figure", name); + return; + } + } + + if (types::is_approximately_equal(a(), b())) { + custom.set("R", R()); + } + else { + custom.set("a", a()); + custom.set("b", b()); + } +} + + +FigureFactory& FigureFactory::instance() { + static FigureFactory obj; + return obj; +} + + +Figure* FigureFactory::make_from_string(const std::string& str) { + std::unique_ptr spec(spec::Custom::make_from_value(YAMLParser::decodeString(str))); + return instance().make_from_spec_(*spec); +} + + +Figure* FigureFactory::make_from_spec_(const Spec& spec) const { + lock_type lock; + + if (std::string figure; spec.get("figure", figure)) { + return Factory
::instance().get(figure).create(spec); + } + + if (double a = 0., b = 0.; spec.get("a", a) && spec.get("b", b)) { + return types::is_approximately_equal(a, b) ? static_cast(new figure::Sphere(a)) + : new figure::OblateSpheroid(a, b); + } + + if (double R = 0.; spec.get("R", R)) { + return new figure::Sphere(R); + } + + Log::error() << "Figure: cannot build figure without 'R' or 'a', 'b'" << std::endl; + throw SpecNotFound("Figure: cannot build figure without 'R' or 'a', 'b'", Here()); +} + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Figure.h b/src/eckit/geo/Figure.h new file mode 100644 index 000000000..3e3f5065c --- /dev/null +++ b/src/eckit/geo/Figure.h @@ -0,0 +1,110 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + +#include "eckit/memory/Builder.h" +#include "eckit/memory/Factory.h" + + +namespace eckit::geo { +namespace area { +class BoundingBox; +} +namespace projection { +class ProjectionOnFigure; +} +namespace spec { +class Custom; +} +class Spec; +} // namespace eckit::geo + + +namespace eckit::geo { + + +/** + * @brief Figure: describe a combination of "shape" (sphere, ellipsoid, geoid) and "size" (radius, a, b, elevation) + */ +class Figure { +public: + // -- Types + + using builder_t = BuilderT1
; + using ARG1 = const Spec&; + + // -- Constructors + + Figure() noexcept = default; + Figure(const Figure&) = delete; + Figure(Figure&&) = delete; + + explicit Figure(const Spec&); + + // -- Destructor + + virtual ~Figure() = default; + + // -- Operators + + Figure& operator=(const Figure&) = delete; + Figure& operator=(Figure&&) = delete; + + // -- Methods + + static std::string className() { return "figure"; } + + virtual double R() const; + virtual double a() const; + virtual double b() const; + + [[nodiscard]] spec::Custom* spec() const; + std::string spec_str() const; + std::string proj_str() const; + + double eccentricity() const; + double flattening() const; + +private: + // -- Methods + + virtual void fill_spec(spec::Custom&) const; + + // -- Friends + + friend bool operator==(const Figure& a, const Figure& b) { return a.spec_str() == b.spec_str(); } + friend bool operator!=(const Figure& a, const Figure& b) { return !(a == b); } + + friend class projection::ProjectionOnFigure; +}; + + +struct FigureFactory { + [[nodiscard]] static Figure* build(const Spec& spec) { return instance().make_from_spec_(spec); } + [[nodiscard]] static Figure* make_from_string(const std::string&); + +private: + static FigureFactory& instance(); + + [[nodiscard]] Figure* make_from_spec_(const Spec&) const; +}; + + +template +using FigureBuilder = ConcreteBuilderT0; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/GreatCircle.cc b/src/eckit/geo/GreatCircle.cc new file mode 100644 index 000000000..13bab2187 --- /dev/null +++ b/src/eckit/geo/GreatCircle.cc @@ -0,0 +1,132 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + +#include "eckit/geo/GreatCircle.h" + +#include +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/util.h" +#include "eckit/geo/util/sincos.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo { + + +using types::is_approximately_equal; + + +static bool is_pole(const double lat) { + return is_approximately_equal(std::abs(lat), 90.); +} + + +GreatCircle::GreatCircle(const PointLonLat& Alonlat, const PointLonLat& Blonlat) : A_(Alonlat), B_(Blonlat) { + const bool Apole = is_pole(A_.lat); + const bool Bpole = is_pole(B_.lat); + const double lon12_deg = PointLonLat::normalise_angle_to_minimum(A_.lon - B_.lon, -PointLonLat::FLAT_ANGLE); + + const bool lon_same = Apole || Bpole || is_approximately_equal(lon12_deg, 0.); + const bool lon_opposite = Apole || Bpole || is_approximately_equal(std::abs(lon12_deg), 180.); + const bool lat_same = is_approximately_equal(A_.lat, B_.lat); + const bool lat_opposite = is_approximately_equal(A_.lat, -B_.lat); + + if ((lat_same && lon_same) || (lat_opposite && lon_opposite)) { + std::ostringstream oss; + oss.precision(std::numeric_limits::max_digits10); + oss << "Great circle cannot be defined by points collinear with the centre, " << A_ << " and " << B_; + throw BadValue(oss.str(), Here()); + } + + crossesPoles_ = lon_same || lon_opposite; +} + + +std::vector GreatCircle::latitude(double lon) const { + if (crossesPoles()) { + return {}; + } + + const double lat1 = util::DEGREE_TO_RADIAN * A_.lat; + const double lat2 = util::DEGREE_TO_RADIAN * B_.lat; + const double lambda1p = util::DEGREE_TO_RADIAN * (lon - A_.lon); + const double lambda2p = util::DEGREE_TO_RADIAN * (lon - B_.lon); + const double lambda + = util::DEGREE_TO_RADIAN * PointLonLat::normalise_angle_to_minimum(B_.lon - A_.lon, -PointLonLat::FLAT_ANGLE); + + double lat + = std::atan((std::tan(lat2) * std::sin(lambda1p) - std::tan(lat1) * std::sin(lambda2p)) / (std::sin(lambda))); + return {util::RADIAN_TO_DEGREE * lat}; +} + + +std::vector GreatCircle::longitude(double lat) const { + if (crossesPoles()) { + const double lon = is_pole(A_.lat) ? B_.lon : A_.lon; + if (is_pole(lat)) { + return {lon}; + } + + return {lon, lon + 180.}; + } + + const double lon12 + = util::DEGREE_TO_RADIAN * PointLonLat::normalise_angle_to_minimum(A_.lon - B_.lon, -PointLonLat::FLAT_ANGLE); + const double lon1 = util::DEGREE_TO_RADIAN * A_.lon; + const double lat1 = util::DEGREE_TO_RADIAN * A_.lat; + const double lat2 = util::DEGREE_TO_RADIAN * B_.lat; + const double lat3 = util::DEGREE_TO_RADIAN * lat; + + const double X = std::sin(lat1) * std::cos(lat2) * std::sin(lon12); + const double Y = std::sin(lat1) * std::cos(lat2) * std::cos(lon12) - std::cos(lat1) * std::sin(lat2); + + if (is_approximately_equal(X, 0.) && is_approximately_equal(Y, 0.)) { + return {}; // parallel (that is, equator) + } + + const double lon0 = lon1 + atan2(Y, X); + const double C = std::cos(lat1) * std::cos(lat2) * std::tan(lat3) * std::sin(lon12) / std::sqrt(X * X + Y * Y); + + if (is_approximately_equal(C, -1.)) { + return {util::RADIAN_TO_DEGREE * (lon0 + M_PI)}; + } + + if (is_approximately_equal(C, 1.)) { + return {util::RADIAN_TO_DEGREE * lon0}; + } + + if (-1 < C && C < 1) { + const double dlon = std::acos(C); + return {util::RADIAN_TO_DEGREE * (lon0 - dlon + 2 * M_PI), util::RADIAN_TO_DEGREE * (lon0 + dlon)}; + } + + return {}; +} + + +bool GreatCircle::crossesPoles() const { + return crossesPoles_; +} + + +std::pair GreatCircle::course() const { + const util::sincos_t dl(util::DEGREE_TO_RADIAN * (B_.lon - A_.lon)); + const util::sincos_t scA(util::DEGREE_TO_RADIAN * A_.lat); + const util::sincos_t scB(util::DEGREE_TO_RADIAN * B_.lat); + + return {util::RADIAN_TO_DEGREE * std::atan2(scB.cos * dl.sin, scA.cos * scB.sin - scA.sin * scB.cos * dl.cos), + util::RADIAN_TO_DEGREE * std::atan2(scA.cos * dl.sin, -scB.cos * scA.sin + scB.sin * scA.cos * dl.cos)}; +} + + +} // namespace eckit::geo diff --git a/src/eckit/geo/GreatCircle.h b/src/eckit/geo/GreatCircle.h new file mode 100644 index 000000000..fed7e941b --- /dev/null +++ b/src/eckit/geo/GreatCircle.h @@ -0,0 +1,56 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + +#include "eckit/geo/PointLonLat.h" + + +namespace eckit::geo { + + +class GreatCircle { +public: + /// Great circle given two points in geographic coordinates + GreatCircle(const PointLonLat&, const PointLonLat&); + + /// Great circle latitude given longitude, see http://www.edwilliams.org/avform.htm#Int + std::vector latitude(double lon) const; + + /// Great circle longitude given latitude, see http://www.edwilliams.org/avform.htm#Par + std::vector longitude(double lat) const; + + /// If great circle crosses the poles (meridian/anti-meridian) + bool crossesPoles() const; + + /** + * @brief Calculate great circle course between two points + * + * @details Calculates the direction (clockwise from North) of a great circle arc between two points. Returns the + * direction (angle) of the arc at each, normalised to the range of atan2 (usually (-180, 180]). All input and + * output values are in units of degrees. + * + * @ref https://en.wikipedia.org/wiki/Great-circle_navigation + */ + std::pair course() const; + +private: + const PointLonLat A_; + const PointLonLat B_; + + bool crossesPoles_; +}; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Grid.cc b/src/eckit/geo/Grid.cc new file mode 100644 index 000000000..e61e68fa0 --- /dev/null +++ b/src/eckit/geo/Grid.cc @@ -0,0 +1,278 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Grid.h" + +#include +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/etc/Grid.h" +#include "eckit/geo/spec/Layered.h" +#include "eckit/geo/util/mutex.h" +#include "eckit/log/Log.h" +#include "eckit/parser/YAMLParser.h" +#include "eckit/utils/MD5.h" + + +namespace eckit::geo { + + +static util::recursive_mutex MUTEX; + + +class lock_type { + util::lock_guard lock_guard_{MUTEX}; +}; + + +Grid::Grid(const Spec& spec) : + bbox_(area::BoundingBox::make_from_spec(spec)), ordering_(make_ordering_from_spec(spec)) {} + + +Grid::Grid(Ordering ordering) : ordering_(ordering) {} + + +Grid::Grid(const area::BoundingBox& bbox, Projection* projection, Ordering ordering) : + bbox_(new area::BoundingBox(bbox)), projection_(projection), ordering_(ordering) {} + + +const Spec& Grid::spec() const { + if (!spec_) { + spec_ = std::make_unique(); + ASSERT(spec_); + + auto& custom = *spec_; + fill_spec(custom); + + if (std::string name; SpecByName::instance().match(custom, name)) { + custom.clear(); + custom.set("grid", name); + } + } + + return *spec_; +} + + +size_t Grid::size() const { + NOTIMP; +} + + +Grid::uid_t Grid::uid() const { + return uid_.empty() ? (uid_ = calculate_uid()) : uid_; +} + + +Grid::uid_t Grid::calculate_uid() const { + auto id = MD5{spec_str()}.digest(); + ASSERT(id.length() == MD5_DIGEST_LENGTH * 2); + return id; +} + + +bool Grid::includesNorthPole() const { + NOTIMP; +} + + +bool Grid::includesSouthPole() const { + NOTIMP; +} + + +bool Grid::isPeriodicWestEast() const { + NOTIMP; +} + + +std::vector Grid::to_points() const { + std::vector points; + points.reserve(size()); + + std::for_each(cbegin(), cend(), [&points](const auto& p) { points.emplace_back(p); }); + + return points; +} + + +std::pair, std::vector > Grid::to_latlon() const { + std::pair, std::vector > ll; + ll.first.reserve(size()); + ll.second.reserve(size()); + + std::for_each(cbegin(), cend(), [&ll](const auto& p) { + auto q = std::get(p); + ll.first.emplace_back(q.lat); + ll.second.emplace_back(q.lon); + }); + + return ll; +} + + +Ordering Grid::ordering() const { + NOTIMP; +} + + +Renumber Grid::reorder(Ordering) const { + NOTIMP; +} + + +Grid* Grid::make_grid_reordered(Ordering) const { + NOTIMP; +} + + +const Area& Grid::area() const { + if (!area_) { + area_ = std::make_unique(); + ASSERT(area_); + } + + return *area_; +} + + +Renumber Grid::crop(const Area&) const { + NOTIMP; +} + + +Grid* Grid::make_grid_cropped(const Area&) const { + NOTIMP; +} + + +const area::BoundingBox& Grid::boundingBox() const { + if (!bbox_) { + bbox_.reset(calculate_bbox()); + ASSERT(bbox_); + } + + return *bbox_; +} + + +area::BoundingBox* Grid::calculate_bbox() const { + NOTIMP; +} + + +Renumber Grid::no_reorder(size_t size) { + Renumber ren(size); + std::iota(ren.begin(), ren.end(), 0); + return ren; +} + + +void Grid::fill_spec(spec::Custom& custom) const { + if (area_) { + static const auto AREA_DEFAULT(area::BOUNDING_BOX_DEFAULT.spec_str()); + + std::unique_ptr area(area_->spec()); + if (area->str() != AREA_DEFAULT) { + custom.set("area", area.release()); + } + } + + if (projection_) { + projection_->fill_spec(custom); + } +} + + +const Grid* GridFactory::make_from_string(const std::string& str) { + std::unique_ptr spec(spec::Custom::make_from_value(YAMLParser::decodeString(str))); + return instance().make_from_spec_(*spec); +} + + +GridFactory& GridFactory::instance() { + static GridFactory obj; + return obj; +} + + +const Grid* GridFactory::make_from_spec_(const Spec& spec) const { + lock_type lock; + + std::unique_ptr cfg(make_spec_(spec)); + + if (std::string type; cfg->get("type", type)) { + return GridFactoryType::instance().get(type).create(*cfg); + } + + list(Log::error() << "Grid: cannot build grid without 'type', choices are: "); + throw SpecNotFound("Grid: cannot build grid without 'type'", Here()); +} + + +Spec* GridFactory::make_spec_(const Spec& spec) const { + lock_type lock; + etc::Grid::instance(); + + auto* cfg = new spec::Layered(spec); + ASSERT(cfg != nullptr); + + + // hardcoded, interpreted options (contributing to gridspec) + + auto back = std::make_unique(); + ASSERT(back); + + if (size_t N = 0; cfg->get("N", N)) { + back->set("grid", "O" + std::to_string(N)); + } + + if (cfg->has("pl")) { + back->set("type", "reduced_gg"); + } + + if (std::vector grid; cfg->get("grid", grid) && grid.size() == 2) { + back->set("type", "regular_ll"); + } + + if (!back->empty()) { + cfg->push_back(back.release()); + } + + if (std::string grid; cfg->get("grid", grid) && SpecByName::instance().matches(grid)) { + cfg->push_back(SpecByName::instance().match(grid).spec(grid)); + } + + if (std::string uid; cfg->get("uid", uid)) { + cfg->push_front(SpecByUID::instance().get(uid).spec()); + } + + + // finalise + + return cfg; +} + + +void GridFactory::list_(std::ostream& out) const { + lock_type lock; + etc::Grid::instance(); + + out << SpecByUID::instance() << std::endl; + out << SpecByName::instance() << std::endl; + out << GridFactoryType::instance() << std::endl; +} + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Grid.h b/src/eckit/geo/Grid.h new file mode 100644 index 000000000..6e2af71a0 --- /dev/null +++ b/src/eckit/geo/Grid.h @@ -0,0 +1,209 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include +#include +#include +#include + +#include "eckit/geo/Area.h" +#include "eckit/geo/Increments.h" +#include "eckit/geo/Iterator.h" +#include "eckit/geo/Ordering.h" +#include "eckit/geo/Point.h" +#include "eckit/geo/Projection.h" +#include "eckit/geo/Renumber.h" +#include "eckit/geo/area/BoundingBox.h" +#include "eckit/geo/projection/Rotation.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/geo/spec/Generator.h" +#include "eckit/memory/Builder.h" +#include "eckit/memory/Factory.h" + + +namespace eckit { +class JSON; +namespace geo { +class Area; +} // namespace geo +} // namespace eckit + + +namespace eckit::geo { + + +class Grid { +public: + // -- Types + + using uid_t = std::string; + using builder_t = BuilderT1; + using ARG1 = const Spec&; + + struct Iterator final : std::unique_ptr { + explicit Iterator(geo::Iterator* it) : unique_ptr(it) { ASSERT(unique_ptr::operator bool()); } + + using difference_type = unique_ptr::element_type::difference_type; + + Iterator(const Iterator&) = delete; + Iterator(Iterator&&) = delete; + + ~Iterator() = default; + + void operator=(const Iterator&) = delete; + void operator=(Iterator&&) = delete; + + bool operator==(const Iterator& other) const { return get()->operator==(*(other.get())); } + bool operator!=(const Iterator& other) const { return get()->operator!=(*(other.get())); } + + bool operator++() { return get()->operator++(); } + bool operator+=(difference_type d) { return get()->operator+=(d); } + + bool operator--() { return get()->operator--(); } + bool operator-=(difference_type d) { return get()->operator-=(d); } + + explicit operator bool() const { return get()->operator bool(); } + Point operator*() const { return get()->operator*(); } + + size_t index() const { return get()->index(); } + }; + + using iterator = Iterator; + + // -- Constructors + + explicit Grid(const Spec&); + + Grid(const Grid&) = delete; + Grid(Grid&&) = delete; + + // -- Destructor + + virtual ~Grid() = default; + + // -- Operators + + Grid& operator=(const Grid&) = delete; + Grid& operator=(Grid&&) = delete; + + // -- Methods + + iterator begin() const { return cbegin(); } + iterator end() const { return cend(); } + + virtual iterator cbegin() const = 0; + virtual iterator cend() const = 0; + + const Spec& spec() const; + std::string spec_str() const { return spec().str(); } + + virtual size_t size() const; + + uid_t uid() const; + [[nodiscard]] virtual uid_t calculate_uid() const; + + virtual bool includesNorthPole() const; + virtual bool includesSouthPole() const; + virtual bool isPeriodicWestEast() const; + + [[nodiscard]] virtual std::vector to_points() const; + [[nodiscard]] virtual std::pair, std::vector> to_latlon() const; + + virtual Ordering ordering() const; + virtual Renumber reorder(Ordering) const; + + virtual const Area& area() const; + virtual Renumber crop(const Area&) const; + + virtual const area::BoundingBox& boundingBox() const; + [[nodiscard]] virtual area::BoundingBox* calculate_bbox() const; + + [[nodiscard]] virtual Grid* make_grid_reordered(Ordering) const; + [[nodiscard]] virtual Grid* make_grid_cropped(const Area&) const; + + // -- Class methods + + static std::string className() { return "grid"; } + +protected: + // -- Constructors + + explicit Grid(const area::BoundingBox&, Projection* = nullptr, Ordering = Ordering::DEFAULT); + explicit Grid(Ordering = Ordering::DEFAULT); + + // -- Methods + + virtual void fill_spec(spec::Custom&) const; + + static Renumber no_reorder(size_t size); + + void area(Area* ptr) { area_.reset(ptr); } + void projection(Projection* ptr) { projection_.reset(ptr); } + +private: + // -- Members + + mutable std::unique_ptr area_; + mutable std::unique_ptr bbox_; + mutable std::unique_ptr projection_; + mutable std::unique_ptr spec_; + mutable uid_t uid_; + + Ordering ordering_; + + // -- Friends + + friend bool operator==(const Grid& a, const Grid& b) { return a.spec_str() == b.spec_str(); } + friend bool operator!=(const Grid& a, const Grid& b) { return !(a == b); } +}; + + +using GridFactoryType = Factory; +using SpecByName = spec::GeneratorT>; +using SpecByUID = spec::GeneratorT; + + +template +using GridRegisterType = ConcreteBuilderT1; + +template +using GridRegisterUID = spec::ConcreteSpecGeneratorT0; + +template +using GridRegisterName = spec::ConcreteSpecGeneratorT1; + + +struct GridFactory { + // This is 'const' as Grid should always be immutable + [[nodiscard]] static const Grid* build(const Spec& spec) { return instance().make_from_spec_(spec); } + + // This is 'const' as Grid should always be immutable + [[nodiscard]] static const Grid* make_from_string(const std::string&); + + [[nodiscard]] static Spec* make_spec(const Spec& spec) { return instance().make_spec_(spec); } + static void list(std::ostream& out) { return instance().list_(out); } + +private: + static GridFactory& instance(); + + // This is 'const' as Grid should always be immutable + [[nodiscard]] const Grid* make_from_spec_(const Spec&) const; + + [[nodiscard]] Spec* make_spec_(const Spec&) const; + void list_(std::ostream&) const; +}; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Increments.cc b/src/eckit/geo/Increments.cc new file mode 100644 index 000000000..33c2aede8 --- /dev/null +++ b/src/eckit/geo/Increments.cc @@ -0,0 +1,55 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Increments.h" + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/Spec.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo { + + +Increments Increments::make_from_spec(const Spec& spec) { + if (std::vector grid; (spec.get("increments", grid) || spec.get("grid", grid)) && grid.size() == 2) { + return {grid[0], grid[1]}; + } + + if (value_type dx = 0, dy = 0; (spec.get("west_east_increment", dx) && spec.get("south_north_increment", dy)) + || (spec.get("dlon", dx) && spec.get("dlat", dy)) + || (spec.get("dx", dx) && spec.get("dy", dy))) { + return {dx, dy}; + } + + throw SpecNotFound( + "'increments' = 'grid' = ['dx', 'dy'] = ['west_east_increment', 'south_north_increment'] = ['dlon', 'dlat'] = " + "['dx', 'dy'] expected", + Here()); +} + + +Increments::Increments(value_type dx, value_type dy) : array{dx, dy} { + if (!(dx != 0) || !(dy != 0)) { + throw BadValue( + "'increments' = 'grid' = ['west_east_increment', 'south_north_increment'] = ['dlon', 'dlat'] = ['dx', " + "'dy'] != 0 expected", + Here()); + } +} + + +bool Increments::operator==(const Increments& other) const { + return types::is_approximately_equal(dx, other.dx) && types::is_approximately_equal(dy, other.dy); +} + +} // namespace eckit::geo diff --git a/src/eckit/geo/Increments.h b/src/eckit/geo/Increments.h new file mode 100644 index 000000000..c606091bf --- /dev/null +++ b/src/eckit/geo/Increments.h @@ -0,0 +1,84 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + + +namespace eckit::geo { +class Spec; +} + + +namespace eckit::geo { + + +class Increments : public std::array { +public: + // -- Types + + using container_type = array; + + // -- Constructors + + explicit Increments(const Spec& spec) : Increments(make_from_spec(spec)) {} + + Increments(value_type dx, value_type dy); + + Increments(const Increments& other) : container_type(other) {} + + Increments(Increments&& other) : container_type(other) {} + + // -- Destructor + + ~Increments() = default; + + // -- Operators + + bool operator==(const Increments& other) const; + bool operator!=(const Increments& other) const { return !operator==(other); } + + Increments& operator=(const Increments& other) { + container_type::operator=(other); + return *this; + } + + Increments& operator=(Increments&& other) { + container_type::operator=(other); + return *this; + } + + // Members + + const value_type& dx = container_type::operator[](0); + const value_type& dy = container_type::operator[](1); + + // -- Methods + + container_type deconstruct() const { return {dx, dy}; } + + // -- Class methods + + static Increments make_from_spec(const Spec&); + +private: + // -- Friends + + friend std::ostream& operator<<(std::ostream& os, const Increments& inc) { + return os << "[" << inc.dx << "," << inc.dy << "]"; + } +}; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Iterator.cc b/src/eckit/geo/Iterator.cc new file mode 100644 index 000000000..ae0400338 --- /dev/null +++ b/src/eckit/geo/Iterator.cc @@ -0,0 +1,31 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Iterator.h" + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo { + + +spec::Custom* Iterator::spec() const { + auto* custom = new spec::Custom; + ASSERT(custom != nullptr); + + fill_spec(*custom); + return custom; +} + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Iterator.h b/src/eckit/geo/Iterator.h new file mode 100644 index 000000000..b4cfb6404 --- /dev/null +++ b/src/eckit/geo/Iterator.h @@ -0,0 +1,84 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + +#include "eckit/geo/Point.h" + + +namespace eckit::geo { +class Grid; +namespace spec { +class Custom; +} +} // namespace eckit::geo + + +namespace eckit::geo { + + +class Iterator { +public: + // -- Types + + using difference_type = std::ptrdiff_t; + + // -- Constructors + + Iterator(const Iterator&) = delete; + Iterator(Iterator&&) = delete; + + // -- Destructor + + virtual ~Iterator() = default; + + // -- Operators + + void operator=(const Iterator&) = delete; + void operator=(Iterator&&) = delete; + + virtual bool operator==(const Iterator&) const = 0; + bool operator!=(const Iterator& other) const { return !operator==(other); } + + virtual bool operator++() = 0; + virtual bool operator+=(difference_type) = 0; + + virtual bool operator--() { return operator-=(1); } + virtual bool operator-=(difference_type diff) { return operator+=(-diff); } + + virtual explicit operator bool() const = 0; + virtual Point operator*() const = 0; + + // -- Methods + + virtual size_t index() const = 0; + + [[nodiscard]] spec::Custom* spec() const; + +protected: + // -- Constructors + + Iterator() = default; + + // -- Methods + + virtual void fill_spec(spec::Custom&) const = 0; + + // -- Friends + + friend class Grid; +}; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/LibEcKitGeo.cc b/src/eckit/geo/LibEcKitGeo.cc new file mode 100644 index 000000000..62e479c6f --- /dev/null +++ b/src/eckit/geo/LibEcKitGeo.cc @@ -0,0 +1,84 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/LibEcKitGeo.h" + +#include "eckit/config/Resource.h" +#include "eckit/eckit_version.h" +#include "eckit/filesystem/PathName.h" +#include "eckit/geo/eckit_geo_config.h" +#include "eckit/utils/StringTools.h" + + +namespace eckit { + + +REGISTER_LIBRARY(LibEcKitGeo); + + +LibEcKitGeo::LibEcKitGeo() : Library("eckit_geo") {} + + +LibEcKitGeo& LibEcKitGeo::instance() { + static LibEcKitGeo lib; + return lib; +} + + +std::vector LibEcKitGeo::etcGrid() { + static const auto paths = [](const std::string& s) -> std::vector { + const auto ss = StringTools::split(":", s); + return {ss.begin(), ss.end()}; + }(LibResource("eckit-geo-etc-grid;$ECKIT_GEO_ETC_GRID", eckit_GEO_ETC_GRID)); + return paths; +} + + +bool LibEcKitGeo::caching() { + static const bool yes{ + LibResource("eckit-geo-caching;$ECKIT_GEO_CACHING", eckit_HAVE_GEO_CACHING != 0)}; + return yes; +} + + +std::string LibEcKitGeo::cacheDir() { + static std::string path = PathName{ + LibResource("eckit-geo-cache-path;$ECKIT_GEO_CACHE_PATH", eckit_GEO_CACHE_PATH)}; + return path; +} + + +bool LibEcKitGeo::proj() { + static const bool yes{ + LibResource("eckit-geo-projection-proj;$ECKIT_GEO_PROJECTION_PROJ", + (eckit_HAVE_PROJ != 0) && (eckit_HAVE_GEO_PROJECTION_PROJ_DEFAULT != 0))}; + return yes; +} + + +const void* LibEcKitGeo::addr() const { + return this; +} + + +std::string LibEcKitGeo::version() const { + return eckit_version_str(); +} + + +std::string LibEcKitGeo::gitsha1(unsigned int count) const { + std::string sha1(eckit_git_sha1()); + return sha1.empty() ? "not available" : sha1.substr(0, std::min(count, 40U)); +} + + +} // namespace eckit diff --git a/src/eckit/geo/LibEcKitGeo.h b/src/eckit/geo/LibEcKitGeo.h new file mode 100644 index 000000000..5a729e6c0 --- /dev/null +++ b/src/eckit/geo/LibEcKitGeo.h @@ -0,0 +1,54 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + +#include "eckit/system/Library.h" + + +namespace eckit { +class PathName; +} + + +namespace eckit { + + +class LibEcKitGeo final : public system::Library { +public: + // -- Methods + + static LibEcKitGeo& instance(); + + static std::vector etcGrid(); + + static bool caching(); + static std::string cacheDir(); + + static bool proj(); + +private: + // -- Constructors + + LibEcKitGeo(); + + // -- Overridden methods + + [[nodiscard]] const void* addr() const override; + std::string version() const override; + std::string gitsha1(unsigned int count) const override; +}; + + +} // namespace eckit diff --git a/src/eckit/geo/Ordering.cc b/src/eckit/geo/Ordering.cc new file mode 100644 index 000000000..f0d7b6e59 --- /dev/null +++ b/src/eckit/geo/Ordering.cc @@ -0,0 +1,96 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Ordering.h" + +#include "eckit/geo/Spec.h" + + +namespace eckit::geo { + + +Ordering make_ordering_from_spec(const Spec& spec) { + static const Ordering SCAN[] = { + scan_i_positively_j_negatively_ij_i_single_direction, + scan_i_negatively_j_negatively_ij_i_single_direction, + scan_i_positively_j_positively_ij_i_single_direction, + scan_i_negatively_j_positively_ij_i_single_direction, + scan_i_positively_j_negatively_ji_i_single_direction, + scan_i_negatively_j_negatively_ji_i_single_direction, + scan_i_positively_j_positively_ji_i_single_direction, + scan_i_negatively_j_positively_ji_i_single_direction, + scan_i_positively_j_negatively_ij_i_alternating_direction, + scan_i_negatively_j_negatively_ij_i_alternating_direction, + scan_i_positively_j_positively_ij_i_alternating_direction, + scan_i_negatively_j_positively_ij_i_alternating_direction, + scan_i_positively_j_negatively_ji_i_alternating_direction, + scan_i_negatively_j_negatively_ji_i_alternating_direction, + scan_i_positively_j_positively_ji_i_alternating_direction, + scan_i_negatively_j_positively_ji_i_alternating_direction, + }; + + int key = (spec.get_bool("scan_i_plus", true) ? 0 : 1) + (spec.get_bool("scan_j_plus", false) ? 1 << 1 : 0) + + (spec.get_bool("scan_ij", true) ? 0 : 1 << 2) + (spec.get_bool("scan_alternating", false) ? 1 << 3 : 0); + + return SCAN[key]; +} + + +bool ordering_is_i_positive(Ordering o) { + return o == scan_i_positively_j_negatively_ij_i_single_direction + || o == scan_i_positively_j_positively_ij_i_single_direction + || o == scan_i_positively_j_negatively_ji_i_single_direction + || o == scan_i_positively_j_positively_ji_i_single_direction + || o == scan_i_positively_j_negatively_ij_i_alternating_direction + || o == scan_i_positively_j_positively_ij_i_alternating_direction + || o == scan_i_positively_j_negatively_ji_i_alternating_direction + || o == scan_i_positively_j_positively_ji_i_alternating_direction; +} + + +bool ordering_is_j_positive(Ordering o) { + return o == scan_i_positively_j_positively_ij_i_single_direction + || o == scan_i_negatively_j_positively_ij_i_single_direction + || o == scan_i_positively_j_positively_ji_i_single_direction + || o == scan_i_negatively_j_positively_ji_i_single_direction + || o == scan_i_positively_j_positively_ij_i_alternating_direction + || o == scan_i_negatively_j_positively_ij_i_alternating_direction + || o == scan_i_positively_j_positively_ji_i_alternating_direction + || o == scan_i_negatively_j_positively_ji_i_alternating_direction; +} + + +bool ordering_is_ij(Ordering o) { + return o == scan_i_positively_j_negatively_ij_i_single_direction + || o == scan_i_negatively_j_negatively_ij_i_single_direction + || o == scan_i_positively_j_positively_ij_i_single_direction + || o == scan_i_negatively_j_positively_ij_i_single_direction + || o == scan_i_positively_j_negatively_ij_i_alternating_direction + || o == scan_i_negatively_j_negatively_ij_i_alternating_direction + || o == scan_i_positively_j_positively_ij_i_alternating_direction + || o == scan_i_negatively_j_positively_ij_i_alternating_direction; +} + + +bool ordering_is_alternating(Ordering o) { + return o == scan_i_positively_j_negatively_ij_i_alternating_direction + || o == scan_i_negatively_j_negatively_ij_i_alternating_direction + || o == scan_i_positively_j_positively_ij_i_alternating_direction + || o == scan_i_negatively_j_positively_ij_i_alternating_direction + || o == scan_i_positively_j_negatively_ji_i_alternating_direction + || o == scan_i_negatively_j_negatively_ji_i_alternating_direction + || o == scan_i_positively_j_positively_ji_i_alternating_direction + || o == scan_i_negatively_j_positively_ji_i_alternating_direction; +} + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Ordering.h b/src/eckit/geo/Ordering.h new file mode 100644 index 000000000..44d3ee073 --- /dev/null +++ b/src/eckit/geo/Ordering.h @@ -0,0 +1,64 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + + +namespace eckit::geo { +class Spec; +} + + +namespace eckit::geo { + + +enum Ordering +{ + scan_i_positively_j_negatively_ij_i_single_direction, + scan_i_negatively_j_negatively_ij_i_single_direction, + scan_i_positively_j_positively_ij_i_single_direction, + scan_i_negatively_j_positively_ij_i_single_direction, + scan_i_positively_j_negatively_ji_i_single_direction, + scan_i_negatively_j_negatively_ji_i_single_direction, + scan_i_positively_j_positively_ji_i_single_direction, + scan_i_negatively_j_positively_ji_i_single_direction, + scan_i_positively_j_negatively_ij_i_alternating_direction, + scan_i_negatively_j_negatively_ij_i_alternating_direction, + scan_i_positively_j_positively_ij_i_alternating_direction, + scan_i_negatively_j_positively_ij_i_alternating_direction, + scan_i_positively_j_negatively_ji_i_alternating_direction, + scan_i_negatively_j_negatively_ji_i_alternating_direction, + scan_i_positively_j_positively_ji_i_alternating_direction, + scan_i_negatively_j_positively_ji_i_alternating_direction, + // TODO regular_ ... shift + + healpix_ring, + healpix_nested, + + scan_ordering = scan_i_positively_j_negatively_ij_i_single_direction, + scan_ordering_end = scan_i_negatively_j_positively_ji_i_alternating_direction, + healpix_ordering = healpix_ring, + healpix_ordering_end = healpix_nested, + + DEFAULT = scan_i_positively_j_negatively_ij_i_single_direction +}; + + +Ordering make_ordering_from_spec(const Spec&); + +bool ordering_is_i_positive(Ordering); +bool ordering_is_j_positive(Ordering); +bool ordering_is_ij(Ordering); +bool ordering_is_alternating(Ordering); + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Point.cc b/src/eckit/geo/Point.cc new file mode 100644 index 000000000..970ab30d7 --- /dev/null +++ b/src/eckit/geo/Point.cc @@ -0,0 +1,41 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Point.h" + +#include + +#include "eckit/exception/Exceptions.h" + + +namespace eckit::geo { + + +bool points_equal(const Point& p, const Point& q) { + ASSERT(p.index() == q.index()); + return std::visit([&](const auto& p, const auto& q) { return points_equal(p, q); }, p, q); +} + + +bool points_equal(const Point& p, const Point& q, double eps) { + ASSERT(p.index() == q.index()); + return std::visit([&](const auto& p, const auto& q) { return points_equal(p, q, eps); }, p, q); +} + + +std::ostream& operator<<(std::ostream& out, const Point& p) { + std::visit([&](const auto& p) { out << p; }, p); + return out; +} + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Point.h b/src/eckit/geo/Point.h new file mode 100644 index 000000000..01059287c --- /dev/null +++ b/src/eckit/geo/Point.h @@ -0,0 +1,35 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + +#include "eckit/geo/Point2.h" +#include "eckit/geo/Point3.h" +#include "eckit/geo/PointLonLat.h" +#include "eckit/geo/PointLonLatR.h" + + +namespace eckit::geo { + + +using Point = std::variant; + +bool points_equal(const Point&, const Point&); +bool points_equal(const Point&, const Point&, double eps); + +std::ostream& operator<<(std::ostream&, const Point&); + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Point2.cc b/src/eckit/geo/Point2.cc new file mode 100644 index 000000000..ce0a4f7f8 --- /dev/null +++ b/src/eckit/geo/Point2.cc @@ -0,0 +1,61 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Point2.h" + +#include + +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo { + + +static const Point2 ZERO; + + +Point2::value_type Point2::norm() const { + return distance(ZERO); +} + + +Point2 Point2::normalize() const { + const auto l = norm(); + return types::is_approximately_equal(l, 0., EPS) ? ZERO : Point2{X / l, Y / l}; +} + + +Point2 Point2::middle(const Point2& p) const { + return (*this + p) * 0.5; +} + + +Point2::value_type Point2::distance(const Point2& p, size_t axis) const { + return std::abs(x(axis) - p.x(axis)); +} + + +Point2::value_type Point2::distance(const Point2& p) const { + return std::sqrt(distance2(p)); +} + + +Point2::value_type Point2::distance2(const Point2& p) const { + return (X - p.X) * (X - p.X) + (Y - p.Y) * (Y - p.Y); +} + + +bool points_equal(const Point2& a, const Point2& b, Point2::value_type eps) { + return types::is_approximately_equal(a.X, b.X, eps) && types::is_approximately_equal(a.Y, b.Y, eps); +} + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Point2.h b/src/eckit/geo/Point2.h new file mode 100644 index 000000000..b0f9651c0 --- /dev/null +++ b/src/eckit/geo/Point2.h @@ -0,0 +1,108 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + + +namespace eckit::geo { + + +/** + * @brief The Point2 class + * @details A point on two-dimensional space, in (X, Y) coordinates, linear in space. + */ +class Point2 final : protected std::array { +public: + // -- Types + + using container_type = array; + using container_type::value_type; + + // -- Constructors + + Point2() : Point2(0., 0.) {} + + Point2(value_type x, value_type y) : container_type{x, y} {} + + Point2(const Point2& other) : container_type(other) {} + + Point2(Point2&& other) : container_type(other) {} + + // -- Destructor + + ~Point2() = default; + + // -- Operators + + using container_type::operator[]; + + Point2& operator=(const Point2& other) { + container_type::operator=(other); + return *this; + } + + Point2& operator=(Point2&& other) { + container_type::operator=(other); + return *this; + } + + // -- Members + + const value_type& X = container_type::operator[](0); + const value_type& Y = container_type::operator[](1); + + // -- Methods + + static size_t dimensions() { return DIMS; } + + static value_type norm(const Point2& p) { return p.norm(); } + static Point2 normalize(const Point2& p) { return p.normalize(); } + static Point2 middle(const Point2& p, const Point2& q) { return p.middle(q); } + static value_type distance(const Point2& p, const Point2& q, size_t axis) { return p.distance(q, axis); } + static value_type distance(const Point2& p, const Point2& q) { return p.distance(q); } + static value_type distance2(const Point2& p, const Point2& q) { return p.distance2(q); } + + value_type norm() const; + Point2 normalize() const; + Point2 middle(const Point2&) const; + value_type distance(const Point2&, size_t axis) const; + value_type distance(const Point2&) const; + value_type distance2(const Point2&) const; + + value_type x(size_t axis) const { return container_type::operator[](axis); } + + // -- Class members + + static constexpr size_t DIMS = 2; + static constexpr value_type EPS = 1e-9; + + // -- Friends + + friend std::ostream& operator<<(std::ostream& out, const Point2& p) { + return out << '{' << p.X << ", " << p.Y << '}'; + } + + friend Point2 operator-(const Point2& p, const Point2& q) { return {p.X - q.X, p.Y - q.Y}; } + friend Point2 operator+(const Point2& p, const Point2& q) { return {p.X + q.X, p.Y + q.Y}; } + friend Point2 operator*(const Point2& p, value_type d) { return {p.X * d, p.Y * d}; } + + friend bool operator==(const Point2& p, const Point2& q) { return p.X == q.X && p.Y == q.Y; } + friend bool operator!=(const Point2& p, const Point2& q) { return !operator==(p, q); } +}; + + +bool points_equal(const Point2&, const Point2&, Point2::value_type eps = Point2::EPS); + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Point3.cc b/src/eckit/geo/Point3.cc new file mode 100644 index 000000000..037b76d9c --- /dev/null +++ b/src/eckit/geo/Point3.cc @@ -0,0 +1,43 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Point3.h" + +#include + +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo { + + +Point3::value_type Point3::distance(const Point3& p, size_t axis) const { + return std::abs(x(axis) - p.x(axis)); +} + + +Point3::value_type Point3::distance(const Point3& p) const { + return std::sqrt(distance2(p)); +} + + +Point3::value_type Point3::distance2(const Point3& p) const { + return (X - p.X) * (X - p.X) + (Y - p.Y) * (Y - p.Y) + (Z - p.Z) * (Z - p.Z); +} + + +bool points_equal(const Point3& a, const Point3& b, Point3::value_type eps) { + return types::is_approximately_equal(a.X, b.X, eps) && types::is_approximately_equal(a.Y, b.Y, eps) + && types::is_approximately_equal(a.Z, b.Z, eps); +} + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Point3.h b/src/eckit/geo/Point3.h new file mode 100644 index 000000000..efdad55b5 --- /dev/null +++ b/src/eckit/geo/Point3.h @@ -0,0 +1,101 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + + +namespace eckit::geo { + + +/** + * @brief The Point3 class + * @details A point on three-dimensional space, in (X, Y, Z) coordinates, linear in space. + */ +class Point3 final : protected std::array { +public: + // -- Types + + using container_type = array; + using container_type::value_type; + + // -- Constructors + + Point3() : Point3(0., 0., 0.) {} + + Point3(value_type x, value_type y, value_type z) : container_type{x, y, z} {} + + Point3(const Point3& other) : container_type(other) {} + + Point3(Point3&& other) : container_type(other) {} + + // -- Destructor + + ~Point3() = default; + + // -- Operators + + using container_type::operator[]; + + Point3& operator=(const Point3& other) { + container_type::operator=(other); + return *this; + } + + Point3& operator=(Point3&& other) { + container_type::operator=(other); + return *this; + } + + // -- Members + + const value_type& X = container_type::operator[](0); + const value_type& Y = container_type::operator[](1); + const value_type& Z = container_type::operator[](2); + + // -- Methods + + static value_type distance(const Point3& p, const Point3& q, size_t axis) { return p.distance(q, axis); } + static value_type distance(const Point3& p, const Point3& q) { return p.distance(q); } + static value_type distance2(const Point3& p, const Point3& q) { return p.distance2(q); } + + value_type distance(const Point3&, size_t axis) const; + value_type distance(const Point3&) const; + value_type distance2(const Point3&) const; + + value_type x(size_t axis) const { return container_type::operator[](axis); } + + // -- Class members + + static constexpr size_t DIMS = 3; + static constexpr value_type EPS = 1e-9; + + // -- Friends + + friend std::ostream& operator<<(std::ostream& out, const Point3& p) { + return out << '{' << p.X << ", " << p.Y << ", " << p.Z << '}'; + } + + friend Point3 operator-(const Point3& p, const Point3& q) { return {p.X - q.X, p.Y - q.Y, p.Z - q.Z}; } + friend Point3 operator+(const Point3& p, const Point3& q) { return {p.X + q.X, p.Y + q.Y, p.Z + q.Z}; } + friend Point3 operator*(const Point3& p, value_type d) { return {p.X * d, p.Y * d, p.Z * d}; } + + friend bool operator==(const Point3& p, const Point3& q) { return p.X == q.X && p.Y == q.Y && p.Z == q.Z; } + friend bool operator!=(const Point3& p, const Point3& q) { return !operator==(p, q); } +}; + + +bool points_equal(const Point3&, const Point3&, Point3::value_type eps = Point3::EPS); + + +} // namespace eckit::geo diff --git a/src/eckit/geo/PointLonLat.cc b/src/eckit/geo/PointLonLat.cc new file mode 100644 index 000000000..ed0cbdbf5 --- /dev/null +++ b/src/eckit/geo/PointLonLat.cc @@ -0,0 +1,95 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/PointLonLat.h" + +#include +#include +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/util.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo { + + +PointLonLat::value_type PointLonLat::normalise_angle_to_minimum(value_type a, value_type minimum) { + const auto modulus = [](auto a) { return a - FULL_ANGLE * std::floor(a / FULL_ANGLE); }; + + auto diff = a - minimum; + return 0. <= diff && diff < FULL_ANGLE ? a : (modulus(diff) + minimum); +} + + +PointLonLat::value_type PointLonLat::normalise_angle_to_maximum(value_type a, value_type maximum) { + const auto modulus = [](auto a) { return a - FULL_ANGLE * std::ceil(a / FULL_ANGLE); }; + + auto diff = a - maximum; + return -FULL_ANGLE < diff && diff <= 0. ? a : (modulus(diff) + maximum); +} + + +void PointLonLat::assert_latitude_range(const PointLonLat& P) { + if (!(-RIGHT_ANGLE <= P.lat && P.lat <= RIGHT_ANGLE)) { + std::ostringstream oss; + oss.precision(std::numeric_limits::max_digits10); + oss << "Invalid latitude [degree] " << P.lat; + throw BadValue(oss.str(), Here()); + } +} + + +PointLonLat PointLonLat::make(value_type lon, value_type lat, value_type lon_minimum, value_type eps) { + lat = normalise_angle_to_minimum(lat, -RIGHT_ANGLE); + + if (types::is_strictly_greater(lat, RIGHT_ANGLE, eps)) { + lat = FLAT_ANGLE - lat; + lon += FLAT_ANGLE; + } + + return types::is_approximately_equal(lat, RIGHT_ANGLE, eps) ? NORTH_POLE + : types::is_approximately_equal(lat, -RIGHT_ANGLE, eps) + ? SOUTH_POLE + : PointLonLat{normalise_angle_to_minimum(lon, lon_minimum), lat}; +} + + +PointLonLat PointLonLat::make_from_lonlatr(value_type lonr, value_type latr, value_type lonr_minimum) { + return make(util::RADIAN_TO_DEGREE * lonr, util::RADIAN_TO_DEGREE * latr, util::RADIAN_TO_DEGREE * lonr_minimum); +} + + +PointLonLat PointLonLat::componentsMin(const PointLonLat& p, const PointLonLat& q) { + return {std::min(p.lon, q.lon), std::min(p.lat, q.lat)}; +} + + +PointLonLat PointLonLat::componentsMax(const PointLonLat& p, const PointLonLat& q) { + return {std::max(p.lon, q.lon), std::max(p.lat, q.lat)}; +} + + +bool points_equal(const PointLonLat& a, const PointLonLat& b, PointLonLat::value_type eps) { + const auto c = PointLonLat::make(a.lon, a.lat, 0., eps); + const auto d = PointLonLat::make(b.lon, b.lat, 0., eps); + return types::is_approximately_equal(c.lon, d.lon, eps) && types::is_approximately_equal(c.lat, d.lat, eps); +} + + +const PointLonLat NORTH_POLE{0., PointLonLat::RIGHT_ANGLE}; +const PointLonLat SOUTH_POLE{0., -PointLonLat::RIGHT_ANGLE}; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/PointLonLat.h b/src/eckit/geo/PointLonLat.h new file mode 100644 index 000000000..aa5dc7b54 --- /dev/null +++ b/src/eckit/geo/PointLonLat.h @@ -0,0 +1,121 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + + +namespace eckit::geo { +class PointLonLatR; +} + + +namespace eckit::geo { + + +/** + * @brief The PointLonLat class + * @details A point on a geographic coordinate system, in (longitude, latitude) coordinates [degree]; They are fully + * circular in space (also latitude), longitude is typically limited to [0, 360[ and latitude to [-90, 90], with + * normalisation functions available, as well as conversion to and from radian-based coordinates. + */ +class PointLonLat final : protected std::array { +public: + // -- Types + + using container_type = array; + using container_type::value_type; + + // -- Constructors + + PointLonLat() : PointLonLat(0., 0.) {} + + PointLonLat(value_type lon, value_type lat) : container_type{lon, lat} {} + + PointLonLat(const PointLonLat& other) : container_type(other) {} + + PointLonLat(PointLonLat&& other) : container_type(other) {} + + // -- Destructor + + ~PointLonLat() = default; + + // -- Operators + + PointLonLat& operator=(const PointLonLat& other) { + container_type::operator=(other); + return *this; + } + + PointLonLat& operator=(PointLonLat&& other) { + container_type::operator=(other); + return *this; + } + + // -- Members + + const value_type& lon = container_type::operator[](0); + const value_type& lat = container_type::operator[](1); + + // -- Methods + + static value_type normalise_angle_to_minimum(value_type, value_type minimum); + + static value_type normalise_angle_to_maximum(value_type, value_type maximum); + + static void assert_latitude_range(const PointLonLat&); + + [[nodiscard]] static PointLonLat make(value_type lon, value_type lat, value_type lon_minimum = 0., + value_type eps = EPS); + + [[nodiscard]] static PointLonLat make_from_lonlatr(value_type lonr, value_type latr, value_type lonr_minimum = 0.); + + PointLonLat antipode() const { return make(lon, lat + FLAT_ANGLE); } + + // -- Class members + + static constexpr value_type FULL_ANGLE = 360.; + static constexpr value_type FLAT_ANGLE = 180.; + static constexpr value_type RIGHT_ANGLE = 90.; + static constexpr value_type EPS = 1e-9; + + // -- Class methods + + static PointLonLat componentsMin(const PointLonLat& p, const PointLonLat& q); + static PointLonLat componentsMax(const PointLonLat& p, const PointLonLat& q); + + // -- Friends + + friend std::ostream& operator<<(std::ostream& out, const PointLonLat& p) { + return out << '{' << p.lon << ", " << p.lat << '}'; + } + + friend PointLonLat operator-(const PointLonLat& p, const PointLonLat& q) { return {p.lon - q.lon, p.lat - q.lat}; } + friend PointLonLat operator+(const PointLonLat& p, const PointLonLat& q) { return {p.lon + q.lon, p.lat + q.lat}; } + friend PointLonLat operator*(const PointLonLat& p, value_type d) { return {p.lon * d, p.lat * d}; } + + friend bool operator<(const PointLonLat& p, const PointLonLat& q) { + return static_cast(p) < static_cast(q); + } +}; + + +bool points_equal(const PointLonLat&, const PointLonLat&, PointLonLat::value_type eps = PointLonLat::EPS); + + +extern const PointLonLat NORTH_POLE; +extern const PointLonLat SOUTH_POLE; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/PointLonLatR.cc b/src/eckit/geo/PointLonLatR.cc new file mode 100644 index 000000000..ae6103e2d --- /dev/null +++ b/src/eckit/geo/PointLonLatR.cc @@ -0,0 +1,69 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/PointLonLatR.h" + +#include "eckit/geo/util.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo { + + +PointLonLatR::value_type PointLonLatR::normalise_angle_to_minimum(value_type a, value_type minimum) { + static const auto modulus = [](auto a) { return a - FULL_ANGLE * std::floor(a / FULL_ANGLE); }; + + auto diff = a - minimum; + return 0. <= diff && diff < FULL_ANGLE ? a : (modulus(diff) + minimum); +} + + +PointLonLatR::value_type PointLonLatR::normalise_angle_to_maximum(value_type a, value_type maximum) { + auto modulus = [](auto a) { return a - FULL_ANGLE * std::ceil(a / FULL_ANGLE); }; + + auto diff = a - maximum; + return -FULL_ANGLE < diff && diff <= 0. ? a : (modulus(diff) + maximum); +} + + +PointLonLatR PointLonLatR::make(value_type lonr, value_type latr, value_type lonr_minimum, value_type eps) { + latr = normalise_angle_to_minimum(latr, -RIGHT_ANGLE); + + if (types::is_strictly_greater(latr, RIGHT_ANGLE, eps)) { + latr = FLAT_ANGLE - latr; + lonr += FLAT_ANGLE; + } + + return types::is_approximately_equal(latr, RIGHT_ANGLE, eps) ? NORTH_POLE_R + : types::is_approximately_equal(latr, -RIGHT_ANGLE, eps) + ? SOUTH_POLE_R + : PointLonLatR{normalise_angle_to_minimum(lonr, lonr_minimum), latr}; +} + + +PointLonLatR PointLonLatR::make_from_lonlat(value_type lon, value_type lat, value_type lon_minimum) { + return make(util::DEGREE_TO_RADIAN * lon, util::DEGREE_TO_RADIAN * lat, util::DEGREE_TO_RADIAN * lon_minimum); +} + + +bool points_equal(const PointLonLatR& a, const PointLonLatR& b, PointLonLatR::value_type eps) { + const auto c = PointLonLatR::make(a.lonr, a.latr, 0., eps); + const auto d = PointLonLatR::make(b.lonr, b.latr, 0., eps); + return types::is_approximately_equal(c.lonr, d.lonr, eps) && types::is_approximately_equal(c.latr, d.latr, eps); +} + + +const PointLonLatR NORTH_POLE_R{0., PointLonLatR::RIGHT_ANGLE}; +const PointLonLatR SOUTH_POLE_R{0., -PointLonLatR::RIGHT_ANGLE}; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/PointLonLatR.h b/src/eckit/geo/PointLonLatR.h new file mode 100644 index 000000000..c44c99bc8 --- /dev/null +++ b/src/eckit/geo/PointLonLatR.h @@ -0,0 +1,110 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include +#include + + +namespace eckit::geo { +class PointLonLat; +} + + +namespace eckit::geo { + + +/** + * @brief The PointLonLatR class + * @details A point on a geographic coordinate system, in (longitude, latitude) coordinates [radian]. They are fully + * circular in space (also latitude), longitude is typically limited to [0, 2 pi[ and latitude to [-pi, pi], with + * normalisation functions available, as well as conversion to and from degree-based coordinates. + */ +class PointLonLatR final : protected std::array { +public: + // -- Types + + using container_type = array; + using container_type::value_type; + + // -- Constructors + + PointLonLatR() : PointLonLatR(0., 0.) {} + + PointLonLatR(value_type lon, value_type lat) : container_type{lon, lat} {} + + PointLonLatR(const PointLonLatR& other) : container_type(other) {} + + PointLonLatR(PointLonLatR&& other) : container_type(other) {} + + // -- Destructor + + ~PointLonLatR() = default; + + // -- Operators + + PointLonLatR& operator=(const PointLonLatR& other) { + container_type::operator=(other); + return *this; + } + + PointLonLatR& operator=(PointLonLatR&& other) { + container_type::operator=(other); + return *this; + } + + // -- Members + + const value_type& lonr = container_type::operator[](0); + const value_type& latr = container_type::operator[](1); + + // -- Methods + + static value_type normalise_angle_to_minimum(value_type, value_type minimum); + + static value_type normalise_angle_to_maximum(value_type, value_type maximum); + + [[nodiscard]] static PointLonLatR make(value_type lonr, value_type latr, value_type lonr_minimum = 0., + value_type eps = EPS); + + [[nodiscard]] static PointLonLatR make_from_lonlat(value_type lon, value_type lat, value_type lon_minimum = 0.); + + PointLonLatR antipode() const { return make(lonr, latr + FLAT_ANGLE); } + + // -- Class members + + static constexpr value_type FULL_ANGLE = 2. * M_PI; + static constexpr value_type FLAT_ANGLE = M_PI; + static constexpr value_type RIGHT_ANGLE = M_PI_2; + static constexpr value_type EPS = 1e-10; + + // -- Class methods + // None + + // -- Friends + + friend std::ostream& operator<<(std::ostream& out, const PointLonLatR& p) { + return out << '{' << p.lonr << ", " << p.latr << '}'; + } +}; + + +bool points_equal(const PointLonLatR&, const PointLonLatR&, PointLonLatR::value_type eps = PointLonLatR::EPS); + + +extern const PointLonLatR NORTH_POLE_R; +extern const PointLonLatR SOUTH_POLE_R; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Projection.cc b/src/eckit/geo/Projection.cc new file mode 100644 index 000000000..5b69d1e11 --- /dev/null +++ b/src/eckit/geo/Projection.cc @@ -0,0 +1,86 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Projection.h" + +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/Figure.h" +#include "eckit/geo/LibEcKitGeo.h" +#include "eckit/geo/eckit_geo_config.h" +#include "eckit/geo/spec/Custom.h" + +#if eckit_HAVE_PROJ +#include "eckit/geo/projection/PROJ.h" +#endif + + +namespace eckit::geo { + + +ProjectionProblem::ProjectionProblem(const std::string& what, const CodeLocation& loc) : Exception(loc) { + reason("ProjectionProblem: [" + what + "], in " + loc.asString()); +}; + + +Figure* Projection::make_figure() const { + NOTIMP; +} + + +const Figure& Projection::figure() const { + if (!figure_) { + figure_.reset(make_figure()); + ASSERT(figure_); + } + + return *figure_; +} + + +spec::Custom* Projection::spec() const { + auto* custom = new spec::Custom; + ASSERT(custom != nullptr); + + fill_spec(*custom); + return custom; +} + + +std::string Projection::spec_str() const { + std::unique_ptr custom(spec()); + return custom->str(); +} + + +std::string Projection::proj_str() const { +#if eckit_HAVE_PROJ + std::unique_ptr custom(spec()); + return projection::PROJ::proj_str(*custom); +#else + NOTIMP; +#endif +} + + +Projection* Projection::make_from_spec(const Spec& spec) { + return ProjectionFactory::instance().get(spec.get_string(LibEcKitGeo::proj() ? "proj" : "projection")).create(spec); +} + + +void Projection::fill_spec(spec::Custom&) const { + NOTIMP; +} + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Projection.h b/src/eckit/geo/Projection.h new file mode 100644 index 000000000..e29a0da8d --- /dev/null +++ b/src/eckit/geo/Projection.h @@ -0,0 +1,105 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + +#include "eckit/geo/Point.h" +#include "eckit/memory/Builder.h" +#include "eckit/memory/Factory.h" + + +namespace eckit::geo { +class Figure; +class Spec; +namespace spec { +class Custom; +} +} // namespace eckit::geo + + +namespace eckit::geo { + + +class ProjectionProblem : public Exception { +public: + explicit ProjectionProblem(const std::string&, const CodeLocation&); +}; + + +class Projection { +public: + // -- Types + + using builder_t = BuilderT1; + using ARG1 = const Spec&; + + // -- Constructors + + Projection() noexcept = default; + Projection(const Projection&) = default; + Projection(Projection&&) = default; + + // -- Destructor + + virtual ~Projection() = default; + + // -- Operators + + Projection& operator=(const Projection&) = default; + Projection& operator=(Projection&&) = default; + + // -- Methods + + virtual Point fwd(const Point&) const = 0; + virtual Point inv(const Point&) const = 0; + + [[nodiscard]] virtual Figure* make_figure() const; + const Figure& figure() const; + + [[nodiscard]] spec::Custom* spec() const; + std::string spec_str() const; + std::string proj_str() const; + + // -- Class methods + + static std::string className() { return "projection"; } + + [[nodiscard]] static Projection* make_from_spec(const Spec&); + +private: + // -- Members + + mutable std::shared_ptr
figure_; + + // -- Methods + + virtual void fill_spec(spec::Custom&) const; + + // -- Friends + + friend class Grid; + + friend bool operator==(const Projection& a, const Projection& b) { return a.spec_str() == b.spec_str(); } + friend bool operator!=(const Projection& a, const Projection& b) { return !(a == b); } +}; + + +using ProjectionFactory = Factory; + +template +using ProjectionBuilder = ConcreteBuilderT1; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Range.cc b/src/eckit/geo/Range.cc new file mode 100644 index 000000000..18ab57d95 --- /dev/null +++ b/src/eckit/geo/Range.cc @@ -0,0 +1,38 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Range.h" + +#include "eckit/exception/Exceptions.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo { + + +Range::Range(size_t n, double _a, double _b, double _eps) : n_(n), a_(_a), b_(_b), eps_(_eps) { + ASSERT(0 < n); + ASSERT(0. <= eps_); + if (types::is_approximately_equal(_a, _b)) { + n_ = 1; + b(_a); + } +} + + +void Range::resize(size_t n) { + ASSERT(0 < n); + n_ = n; +} + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Range.h b/src/eckit/geo/Range.h new file mode 100644 index 000000000..7930afb1f --- /dev/null +++ b/src/eckit/geo/Range.h @@ -0,0 +1,79 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + + +namespace eckit { +class Fraction; +} + + +namespace eckit::geo { + + +class Range { +public: + // -- Constructors + + Range(const Range&) = delete; + Range(Range&&) = delete; + + // -- Destructors + + virtual ~Range() = default; + + // -- Operators + + Range& operator=(const Range&) = delete; + Range& operator=(Range&&) = delete; + + // -- Methods + + size_t size() const { return n_; } + double a() const { return a_; } + double b() const { return b_; } + double eps() const { return eps_; } + + virtual bool periodic() const { return false; } + + [[nodiscard]] virtual Range* make_range_flipped() const = 0; + [[nodiscard]] virtual Range* make_range_cropped(double crop_a, double crop_b) const = 0; + + virtual Fraction increment() const = 0; + virtual const std::vector& values() const = 0; + +protected: + // -- Constructors + + explicit Range(size_t n, double a, double b, double eps = 0.); + + // --Methods + + void resize(size_t n); + void a(double value) { a_ = value; } + void b(double value) { b_ = value; } + +private: + // -- Members + + size_t n_; + double a_; + double b_; + const double eps_; +}; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Renumber.h b/src/eckit/geo/Renumber.h new file mode 100644 index 000000000..e0c7c92c0 --- /dev/null +++ b/src/eckit/geo/Renumber.h @@ -0,0 +1,24 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + + +namespace eckit::geo { + + +using Renumber = std::vector; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Search.h b/src/eckit/geo/Search.h new file mode 100644 index 000000000..7225488fc --- /dev/null +++ b/src/eckit/geo/Search.h @@ -0,0 +1,83 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + +#include "eckit/container/KDTree.h" +#include "eckit/container/sptree/SPValue.h" +#include "eckit/geo/Point.h" +#include "eckit/geo/geometry/UnitSphere.h" + + +namespace eckit::geo { + + +namespace search { +template +struct Traits { + using Point = PointT; + using Payload = PayloadT; +}; +} // namespace search + + +using Search3 = KDTreeMemory>; + + +using Search2 = KDTreeMemory>; + + +struct SearchLonLat : Search3 { + using Point = geo::PointLonLat; + using Value = SPValue, KDMemory>>; + + using Search3::Search3; + + void insert(const SearchLonLat::Value& value) { Search3::insert({to_cartesian(value.point()), value.payload()}); } + + template + void build(const Container& c) { + size_t index = 0; + for (const auto& p : c) { + insert({std::get(p), index++}); + } + } + + size_t nearestNeighbour(const Point& p) { + auto n = Search3::nearestNeighbour(to_cartesian(p)); + return n.payload(); + } + + std::vector findInSphere(const Point& p, double radius) { + std::vector near; + for (auto& n : Search3::findInSphere(to_cartesian(p), radius)) { + near.emplace_back(n.payload()); + } + return near; + } + + std::vector kNearestNeighbours(const Point& p, size_t k) { + std::vector near; + for (auto& n : Search3::kNearestNeighbours(to_cartesian(p), k)) { + near.emplace_back(n.payload()); + } + return near; + } + +private: + static Search3::Point to_cartesian(const Point& p) { return geometry::UnitSphere::convertSphericalToCartesian(p); } +}; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Shape.cc b/src/eckit/geo/Shape.cc new file mode 100644 index 000000000..06e8bfbc8 --- /dev/null +++ b/src/eckit/geo/Shape.cc @@ -0,0 +1,43 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Shape.h" + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/Spec.h" + + +namespace eckit::geo { + + +Shape Shape::make_from_spec(const Spec& spec) { + if (std::vector shape; spec.get("shape", shape) && shape.size() == 2) { + return {shape[0], shape[1]}; + } + + if (value_type nx = 0, ny = 0; + (spec.get("nlon", nx) && spec.get("nlat", ny)) || (spec.get("nlon", nx) && spec.get("nlat", ny))) { + return {nx, ny}; + } + + throw SpecNotFound("'shape' = ['nlon', 'nlat'] = ['nx', 'ny'] expected", Here()); +} + + +Shape::Shape(value_type nx, value_type ny) : array{nx, ny} { + if (!(nx > 0) || !(ny > 0)) { + throw BadValue("'shape' = ['nlon', 'nlat'] = ['nx', 'ny'] > 0 expected", Here()); + } +} + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Shape.h b/src/eckit/geo/Shape.h new file mode 100644 index 000000000..dfc2d59f2 --- /dev/null +++ b/src/eckit/geo/Shape.h @@ -0,0 +1,87 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + + +namespace eckit::geo { +class Spec; +} + + +namespace eckit::geo { + + +class Shape final : public std::array { +public: + // -- Types + + using container_type = array; + + // -- Constructors + + explicit Shape(const Spec& spec) : Shape(make_from_spec(spec)) {} + + Shape(value_type nx, value_type ny); + + Shape() : Shape(0, 0) {} + + Shape(const Shape& other) : container_type(other) {} + + Shape(Shape&& other) : container_type(other) {} + + // -- Destructor + + ~Shape() = default; + + // -- Operators + + bool operator==(const Shape& other) const { return nx == other.nx && ny == other.ny; } + + bool operator!=(const Shape& other) const { return !operator==(other); } + + Shape& operator=(const Shape& other) { + container_type::operator=(other); + return *this; + } + + Shape& operator=(Shape&& other) { + container_type::operator=(other); + return *this; + } + + // Members + + const value_type& nx = container_type::operator[](0); + const value_type& ny = container_type::operator[](1); + + // -- Methods + + container_type deconstruct() const { return {nx, ny}; } + + // -- Class methods + + static Shape make_from_spec(const Spec&); + +private: + // -- Friends + + friend std::ostream& operator<<(std::ostream& os, const Shape& inc) { + return os << "[" << inc.nx << "," << inc.ny << "]"; + } +}; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Spec.cc b/src/eckit/geo/Spec.cc new file mode 100644 index 000000000..5f5d8e89d --- /dev/null +++ b/src/eckit/geo/Spec.cc @@ -0,0 +1,146 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Spec.h" + +#include + +#include "eckit/log/JSON.h" + + +namespace eckit::geo { + + +template +static T _get_d(const Spec& spec, const std::string& name, const T& _default) { + T value{_default}; + spec.get(name, value); + return value; +} + + +template +static T _get_t(const Spec& spec, const std::string& name) { + T value{}; + return spec.get(name, value) ? value : throw SpecNotFound(name, Here()); +} + + +SpecNotFound::SpecNotFound(const std::string& name, const CodeLocation& loc) : Exception(loc) { + reason("SpecNotFound: [" + name + "], in " + loc.asString()); +} + + +std::string Spec::get_string(const std::string& name) const { + return _get_t(*this, name); +} + + +bool Spec::get_bool(const std::string& name) const { + return _get_t(*this, name); +} + + +int Spec::get_int(const std::string& name) const { + return _get_t(*this, name); +} + + +long Spec::get_long(const std::string& name) const { + return _get_t(*this, name); +} + + +size_t Spec::get_unsigned(const std::string& name) const { + return _get_t(*this, name); +} + + +double Spec::get_double(const std::string& name) const { + return _get_t(*this, name); +} + + +std::vector Spec::get_long_vector(const std::string& name) const { + return _get_t>(*this, name); +} + + +std::vector Spec::get_unsigned_vector(const std::string& name) const { + return _get_t>(*this, name); +} + + +std::vector Spec::get_double_vector(const std::string& name) const { + return _get_t>(*this, name); +} + + +std::string Spec::get_string(const std::string& name, const std::string& _default) const { + return _get_d(*this, name, _default); +} + + +bool Spec::get_bool(const std::string& name, const bool& _default) const { + return _get_d(*this, name, _default); +} + + +int Spec::get_int(const std::string& name, const int& _default) const { + return _get_d(*this, name, _default); +} + + +long Spec::get_long(const std::string& name, const long& _default) const { + return _get_d(*this, name, _default); +} + + +size_t Spec::get_unsigned(const std::string& name, const size_t& _default) const { + return _get_d(*this, name, _default); +} + + +double Spec::get_double(const std::string& name, const double& _default) const { + return _get_d(*this, name, _default); +} + + +std::vector Spec::get_long_vector(const std::string& name, const std::vector& _default) const { + return _get_d(*this, name, _default); +} + + +std::vector Spec::get_unsigned_vector(const std::string& name, const std::vector& _default) const { + return _get_d(*this, name, _default); +} + + +std::vector Spec::get_double_vector(const std::string& name, const std::vector& _default) const { + return _get_d(*this, name, _default); +} + + +std::string Spec::str() const { + std::ostringstream str; + JSON j(str); + json(j); + return str.str(); +} + + +void Spec::print(std::ostream& out) const { + JSON j(out); + json(j); +} + + +} // namespace eckit::geo diff --git a/src/eckit/geo/Spec.h b/src/eckit/geo/Spec.h new file mode 100644 index 000000000..ef52b6562 --- /dev/null +++ b/src/eckit/geo/Spec.h @@ -0,0 +1,81 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + +#include "eckit/config/Parametrisation.h" +#include "eckit/exception/Exceptions.h" + + +namespace eckit { +class JSON; +} + + +namespace eckit::geo { + + +class SpecNotFound : public Exception { +public: + explicit SpecNotFound(const std::string&, const CodeLocation&); +}; + + +class Spec : public Parametrisation { +public: + Spec() = default; + ~Spec() override = default; + + Spec(const Spec&) = delete; + Spec(Spec&&) = delete; + + Spec& operator=(const Spec&) = delete; + Spec& operator=(Spec&&) = delete; + + std::string get_string(const std::string& name) const; + bool get_bool(const std::string& name) const; + int get_int(const std::string& name) const; + long get_long(const std::string& name) const; + size_t get_unsigned(const std::string& name) const; + double get_double(const std::string& name) const; + + std::vector get_long_vector(const std::string& name) const; + std::vector get_unsigned_vector(const std::string& name) const; + std::vector get_double_vector(const std::string& name) const; + + std::string get_string(const std::string& name, const std::string&) const; + bool get_bool(const std::string& name, const bool&) const; + int get_int(const std::string& name, const int&) const; + long get_long(const std::string& name, const long&) const; + size_t get_unsigned(const std::string& name, const size_t&) const; + double get_double(const std::string& name, const double&) const; + + std::vector get_long_vector(const std::string& name, const std::vector&) const; + std::vector get_unsigned_vector(const std::string& name, const std::vector&) const; + std::vector get_double_vector(const std::string& name, const std::vector&) const; + + std::string str() const; + + virtual void json(JSON&) const = 0; + +private: + virtual void print(std::ostream&) const; + + friend std::ostream& operator<<(std::ostream& out, const Spec& spec) { + spec.print(out); + return out; + } +}; + + +} // namespace eckit::geo diff --git a/src/eckit/geo/area/BoundingBox.cc b/src/eckit/geo/area/BoundingBox.cc new file mode 100644 index 000000000..f859df3fc --- /dev/null +++ b/src/eckit/geo/area/BoundingBox.cc @@ -0,0 +1,225 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/area/BoundingBox.h" + +#include +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/geometry/Sphere.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::area { + + +const BoundingBox BOUNDING_BOX_DEFAULT; + + +static inline bool is_approximately_equal(BoundingBox::value_type a, BoundingBox::value_type b) { + return types::is_approximately_equal(a, b, PointLonLat::EPS); +} + + +BoundingBox* BoundingBox::make_global_prime() { + return new BoundingBox{PointLonLat::RIGHT_ANGLE, 0., -PointLonLat::RIGHT_ANGLE, PointLonLat::FULL_ANGLE}; +} + + +BoundingBox* BoundingBox::make_global_antiprime() { + return new BoundingBox{PointLonLat::RIGHT_ANGLE, -PointLonLat::FLAT_ANGLE, -PointLonLat::RIGHT_ANGLE, + PointLonLat::FLAT_ANGLE}; +} + + +void BoundingBox::fill_spec(spec::Custom& custom) const { + if (operator!=(BOUNDING_BOX_DEFAULT)) { + custom.set("area", "bounding-box"); + custom.set("bounding-box", std::vector{north, west, south, east}); + } +} + + +BoundingBox* BoundingBox::make_from_spec(const Spec& spec) { + auto [n, w, s, e] = BOUNDING_BOX_DEFAULT.deconstruct(); + + if (std::vector area{n, w, s, e}; spec.get("area", area)) { + ASSERT_MSG(area.size() == 4, "BoundingBox: 'area' expected list of size 4"); + return make_from_area(area[0], area[1], area[2], area[3]); + } + + spec.get("north", n); + spec.get("south", s); + + if (spec.get("west", w) && !spec.has("east")) { + e = w + PointLonLat::FULL_ANGLE; + } + + if (spec.get("east", e) && !spec.has("west")) { + w = e - PointLonLat::FULL_ANGLE; + } + + return make_from_area(n, w, s, e); +} + + +BoundingBox* BoundingBox::make_from_area(value_type n, value_type w, value_type s, value_type e) { + // set latitudes inside usual range (not a normalisation like PointLonLat::make) + if (n > NORTH_POLE.lat || is_approximately_equal(n, NORTH_POLE.lat)) { + n = NORTH_POLE.lat; + } + + if (s < SOUTH_POLE.lat || is_approximately_equal(s, SOUTH_POLE.lat)) { + s = SOUTH_POLE.lat; + } + + // normalise west in [min, min + 2 pi[, east in [west, west + 2 pi[ + constexpr auto min = BOUNDING_BOX_NORMALISE_WEST; + const auto same = is_approximately_equal(w, e); + + w = is_approximately_equal(w, min) || is_approximately_equal(w, min + PointLonLat::FULL_ANGLE) + ? min + : PointLonLat::normalise_angle_to_minimum(w, min); + + auto a = PointLonLat::normalise_angle_to_minimum(e, w); + e = same ? w : is_approximately_equal(w, a) ? (w + PointLonLat::FULL_ANGLE) : a; + + return new BoundingBox{n, w, s, e}; +} + + +BoundingBox::BoundingBox(const Spec& spec) : BoundingBox(*std::unique_ptr(make_from_spec(spec))) {} + + +BoundingBox::BoundingBox(double n, double w, double s, double e) : array{n, w, s, e} { + // normalise east in [west, west + 2 pi[ + auto a = PointLonLat::normalise_angle_to_minimum(e, w); + operator[](3) = is_approximately_equal(w, e) ? w : is_approximately_equal(w, a) ? (w + PointLonLat::FULL_ANGLE) : a; + + ASSERT(south <= north); + ASSERT(west <= east); +} + + +BoundingBox::BoundingBox() : BoundingBox(*std::unique_ptr(make_global_prime())) {} + + +bool BoundingBox::global() const { + return periodic() && contains(NORTH_POLE) && contains(SOUTH_POLE); +} + + +bool BoundingBox::periodic() const { + return west != east && is_approximately_equal(west, PointLonLat::normalise_angle_to_minimum(east, west)); +} + + +bool BoundingBox::contains(const PointLonLat& p) const { + // NOTE: latitudes < -90 or > 90 are not considered + if (is_approximately_equal(p.lat, NORTH_POLE.lat)) { + return is_approximately_equal(p.lat, north); + } + + if (is_approximately_equal(p.lat, SOUTH_POLE.lat)) { + return is_approximately_equal(p.lat, south); + } + + if ((south < p.lat && p.lat < north) || is_approximately_equal(p.lat, north) + || is_approximately_equal(p.lat, south)) { + return PointLonLat::normalise_angle_to_minimum(p.lon, west) <= east; + } + + return false; +} + + +bool BoundingBox::contains(const BoundingBox& other) const { + if (other.empty()) { + return contains({other.south, other.west}); + } + + // check for West/East range (if non-periodic), then other's corners + if (east - west < other.east - other.west || east < PointLonLat::normalise_angle_to_minimum(other.east, west)) { + return false; + } + + return contains({other.north, other.west}) && contains({other.north, other.east}) + && contains({other.south, other.west}) && contains({other.south, other.east}); +} + + +bool BoundingBox::intersects(BoundingBox& other) const { + auto n = std::min(north, other.north); + auto s = std::max(south, other.south); + + bool intersectsSN = s <= n; + if (!intersectsSN) { + n = s; + } + + if (periodic() && other.periodic()) { + other = {n, other.west, s, other.east}; + return intersectsSN; + } + + auto w = std::min(west, other.west); + auto e = w; + + auto intersect = [](const BoundingBox& a, const BoundingBox& b, double& w, double& e) { + bool p = a.periodic(); + if (p || b.periodic()) { + w = (p ? b : a).west; + e = (p ? b : a).east; + return true; + } + + auto ref = PointLonLat::normalise_angle_to_minimum(b.west, a.west); + auto w_ = std::max(a.west, ref); + auto e_ = std::min(a.east, PointLonLat::normalise_angle_to_minimum(b.east, ref)); + + if (w_ <= e_) { + w = w_; + e = e_; + return true; + } + + return false; + }; + + bool intersectsWE = west <= other.west ? intersect(*this, other, w, e) || intersect(other, *this, w, e) + : intersect(other, *this, w, e) || intersect(*this, other, w, e); + + ASSERT_MSG(w <= e, "BoundingBox::intersects: longitude range"); + other = {n, w, s, e}; + + return intersectsSN && intersectsWE; +} + + +bool BoundingBox::empty() const { + return is_approximately_equal(north, south) || is_approximately_equal(west, east); +} + + +bool bounding_box_equal(const BoundingBox& a, const BoundingBox& b) { + const std::unique_ptr c(BoundingBox::make_from_area(a.north, a.west, a.south, a.east)); + const std::unique_ptr d(BoundingBox::make_from_area(b.north, b.west, b.south, b.east)); + + return is_approximately_equal(c->north, d->north) && is_approximately_equal(c->south, d->south) + && is_approximately_equal(c->west, d->west) && is_approximately_equal(c->east, d->east); +} + + +} // namespace eckit::geo::area diff --git a/src/eckit/geo/area/BoundingBox.h b/src/eckit/geo/area/BoundingBox.h new file mode 100644 index 000000000..8a2c02490 --- /dev/null +++ b/src/eckit/geo/area/BoundingBox.h @@ -0,0 +1,111 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + +#include "eckit/geo/Area.h" +#include "eckit/geo/PointLonLat.h" + + +namespace eckit::geo::area { + + +class BoundingBox; + +bool bounding_box_equal(const BoundingBox&, const BoundingBox&); + + +class BoundingBox : public Area, protected std::array { +public: + // -- Types + + using container_type = array; + using value_type = container_type::value_type; + + // -- Constructors + + explicit BoundingBox(const Spec&); + + BoundingBox(value_type north, value_type west, value_type south, value_type east); + + BoundingBox(); + + BoundingBox(const BoundingBox& other) : Area(other), container_type(other) {} + + BoundingBox(BoundingBox&& other) : Area(other), container_type(other) {} + + // -- Destructor + + ~BoundingBox() override = default; + + // -- Operators + + bool operator==(const BoundingBox& other) const { return bounding_box_equal(*this, other); } + bool operator!=(const BoundingBox& other) const { return !operator==(other); } + + BoundingBox& operator=(const BoundingBox& other) { + container_type::operator=(other); + return *this; + } + + BoundingBox& operator=(BoundingBox&& other) { + container_type::operator=(other); + return *this; + } + + // -- Methods + + container_type deconstruct() const { return {north, west, south, east}; } + + bool global() const; + bool periodic() const; + + bool contains(const PointLonLat&) const; + bool contains(const BoundingBox&) const; + bool empty() const; + + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; + bool intersects(BoundingBox&) const override; + + // -- Class methods + + [[nodiscard]] static BoundingBox* make_global_prime(); + [[nodiscard]] static BoundingBox* make_global_antiprime(); + [[nodiscard]] static BoundingBox* make_from_spec(const Spec&); + [[nodiscard]] static BoundingBox* make_from_area(value_type n, value_type w, value_type s, value_type e); + + // -- Members + + const value_type& north = operator[](0); + const value_type& west = operator[](1); + const value_type& south = operator[](2); + const value_type& east = operator[](3); + +private: + // -- Friends + + friend std::ostream& operator<<(std::ostream& os, const BoundingBox& bbox) { + return os << "[" << bbox.north << "," << bbox.west << "," << bbox.south << "," << bbox.east << "]"; + } +}; + + +constexpr PointLonLat::value_type BOUNDING_BOX_NORMALISE_WEST = -PointLonLat::FLAT_ANGLE; +extern const BoundingBox BOUNDING_BOX_DEFAULT; + + +} // namespace eckit::geo::area diff --git a/src/eckit/geo/eckit_geo_config.h.in b/src/eckit/geo/eckit_geo_config.h.in new file mode 100644 index 000000000..11f373160 --- /dev/null +++ b/src/eckit/geo/eckit_geo_config.h.in @@ -0,0 +1,25 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#cmakedefine01 eckit_HAVE_ECKIT_CODEC +#cmakedefine01 eckit_HAVE_GEO_BITREPRODUCIBLE +#cmakedefine01 eckit_HAVE_GEO_CACHING +#cmakedefine01 eckit_HAVE_GEO_CONVEX_HULL +#cmakedefine01 eckit_HAVE_GEO_GRID_ORCA +#cmakedefine01 eckit_HAVE_GEO_PROJECTION_PROJ_DEFAULT +#cmakedefine eckit_GEO_CACHE_PATH "@eckit_GEO_CACHE_PATH@" +#cmakedefine eckit_GEO_ETC_GRID "@eckit_GEO_ETC_GRID@" + +#cmakedefine01 eckit_HAVE_PROJ + diff --git a/src/eckit/geo/etc/Grid.cc b/src/eckit/geo/etc/Grid.cc new file mode 100644 index 000000000..5c4d0f6c3 --- /dev/null +++ b/src/eckit/geo/etc/Grid.cc @@ -0,0 +1,98 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/etc/Grid.h" + +#include "eckit/exception/Exceptions.h" +#include "eckit/filesystem/PathName.h" +#include "eckit/geo/Grid.h" +#include "eckit/geo/LibEcKitGeo.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/parser/YAMLParser.h" +#include "eckit/value/Value.h" + + +namespace eckit::geo::etc { + + +const Grid& Grid::instance() { + static const Grid INSTANCE(LibEcKitGeo::etcGrid()); + return INSTANCE; +} + + +Grid::Grid(const std::vector& paths) { + spec_ = std::make_unique(); + + for (const auto& path : paths) { + if (path.exists()) { + load(path); + } + } +} + + +void Grid::load(const PathName& path) { + auto* custom = dynamic_cast((spec_ ? spec_ : (spec_ = std::make_unique())).get()); + ASSERT(custom != nullptr); + + struct SpecByUIDGenerator final : SpecByUID::generator_t { + explicit SpecByUIDGenerator(spec::Custom* spec) : spec_(spec) { ASSERT(spec_); } + Spec* spec() const override { return new spec::Custom(spec_->container()); } + bool match(const spec::Custom& other) const override { return other == *spec_; } + + private: + std::unique_ptr spec_; + }; + + struct SpecByNameGenerator final : SpecByName::generator_t { + explicit SpecByNameGenerator(spec::Custom* spec) : spec_(spec) { ASSERT(spec_); } + Spec* spec(SpecByName::generator_t::arg1_t) const override { return new spec::Custom(spec_->container()); } + bool match(const spec::Custom& other) const override { return other == *spec_; } + + private: + std::unique_ptr spec_; + }; + + if (path.exists()) { + ValueMap map(YAMLParser::decodeFile(path)); + + for (const auto& kv : map) { + const auto key = kv.first.as(); + + if (key == "grid_uids") { + for (ValueMap m : kv.second.as()) { + ASSERT(m.size() == 1); + SpecByUID::instance().regist( + m.begin()->first.as(), + new SpecByUIDGenerator(spec::Custom::make_from_value(m.begin()->second))); + } + continue; + } + + if (key == "grid_names") { + for (ValueMap m : kv.second.as()) { + ASSERT(m.size() == 1); + SpecByName::instance().regist( + m.begin()->first.as(), + new SpecByNameGenerator(spec::Custom::make_from_value(m.begin()->second))); + } + continue; + } + + custom->set(key, kv.second); + } + } +} + + +} // namespace eckit::geo::etc diff --git a/src/eckit/geo/etc/Grid.h b/src/eckit/geo/etc/Grid.h new file mode 100644 index 000000000..0bc4c7319 --- /dev/null +++ b/src/eckit/geo/etc/Grid.h @@ -0,0 +1,49 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + + +namespace eckit { +namespace geo { +class Spec; +} +class PathName; +} // namespace eckit + + +namespace eckit::geo::etc { + + +class Grid final { +public: + static const Grid& instance(); + +private: + // -- Constructors + + explicit Grid(const std::vector&); + + // -- Members + + std::unique_ptr spec_; + + // -- Methods + + void load(const PathName&); +}; + + +} // namespace eckit::geo::etc diff --git a/src/eckit/geo/figure/Earth.cc b/src/eckit/geo/figure/Earth.cc new file mode 100644 index 000000000..2c8a70bf5 --- /dev/null +++ b/src/eckit/geo/figure/Earth.cc @@ -0,0 +1,24 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/figure/Earth.h" + + +namespace eckit::geo::figure { + + +static const ConcreteBuilderT1 REGISTER1("earth"); +static const ConcreteBuilderT1 REGISTER2("grs80"); +static const ConcreteBuilderT1 REGISTER3("wgs84"); + + +} // namespace eckit::geo::figure diff --git a/src/eckit/geo/figure/Earth.h b/src/eckit/geo/figure/Earth.h new file mode 100644 index 000000000..996d4006a --- /dev/null +++ b/src/eckit/geo/figure/Earth.h @@ -0,0 +1,62 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/figure/OblateSpheroid.h" +#include "eckit/geo/figure/Sphere.h" + + +namespace eckit::geo::figure { + + +struct DatumIFS { + static constexpr double radius = 6371229.; +}; + + +struct DatumGRIB1 { + static constexpr double radius = 6367470.; +}; + + +struct DatumGRS80 { + static constexpr double a = 6378137.; + static constexpr double b = 6356752.314140; +}; + + +struct DatumWGS84 { + static constexpr double a = 6378137.; + static constexpr double b = 6356752.314245; +}; + + +struct Earth final : public Sphere { + explicit Earth() : Sphere(DatumIFS::radius) {} + explicit Earth(const Spec&) : Earth() {} +}; + + +struct GRS80 final : public OblateSpheroid { + explicit GRS80() : OblateSpheroid(DatumGRS80::a, DatumGRS80::b) {} + explicit GRS80(const Spec&) : GRS80() {} +}; + + +struct WGS84 final : public OblateSpheroid { + explicit WGS84() : OblateSpheroid(DatumWGS84::a, DatumWGS84::b) {} + explicit WGS84(const Spec&) : WGS84() {} +}; + + +} // namespace eckit::geo::figure diff --git a/src/eckit/geo/figure/OblateSpheroid.cc b/src/eckit/geo/figure/OblateSpheroid.cc new file mode 100644 index 000000000..86e796358 --- /dev/null +++ b/src/eckit/geo/figure/OblateSpheroid.cc @@ -0,0 +1,42 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/figure/OblateSpheroid.h" + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/Spec.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::figure { + + +OblateSpheroid::OblateSpheroid(double a, double b) : a_(a), b_(b) { + if (!(0. < b && b_ <= a_)) { + throw BadValue("OblateSpheroid::R requires 0 < b <= a", Here()); + } +} + + +OblateSpheroid::OblateSpheroid(const Spec& spec) : OblateSpheroid(spec.get_double("a"), spec.get_double("b")) {} + + +double OblateSpheroid::R() const { + if (types::is_approximately_equal(a_, b_)) { + return a_; + } + + throw BadValue("OblateSpheroid::R requires a ~= b", Here()); +} + + +} // namespace eckit::geo::figure diff --git a/src/eckit/geo/figure/OblateSpheroid.h b/src/eckit/geo/figure/OblateSpheroid.h new file mode 100644 index 000000000..f061c11da --- /dev/null +++ b/src/eckit/geo/figure/OblateSpheroid.h @@ -0,0 +1,42 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/Figure.h" + + +namespace eckit::geo::figure { + + +class OblateSpheroid : public Figure { +public: + // -- Constructors + + OblateSpheroid(double a, double b); + explicit OblateSpheroid(const Spec&); + + // -- Overridden methods + + double R() const override; + double a() const override { return a_; } + double b() const override { return b_; } + +private: + // -- Members + + const double a_; + const double b_; +}; + + +} // namespace eckit::geo::figure diff --git a/src/eckit/geo/figure/Sphere.cc b/src/eckit/geo/figure/Sphere.cc new file mode 100644 index 000000000..663a3b56c --- /dev/null +++ b/src/eckit/geo/figure/Sphere.cc @@ -0,0 +1,32 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/figure/Sphere.h" + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/Spec.h" + + +namespace eckit::geo::figure { + + +Sphere::Sphere(double R) : R_(R) { + if (!(0. < R_)) { + throw BadValue("Sphere::R requires 0 < R", Here()); + } +} + + +Sphere::Sphere(const Spec& spec) : Sphere(spec.get_double("R")) {} + + +} // namespace eckit::geo::figure diff --git a/src/eckit/geo/figure/Sphere.h b/src/eckit/geo/figure/Sphere.h new file mode 100644 index 000000000..162d08f04 --- /dev/null +++ b/src/eckit/geo/figure/Sphere.h @@ -0,0 +1,41 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/Figure.h" + + +namespace eckit::geo::figure { + + +class Sphere : public Figure { +public: + // -- Constructors + + explicit Sphere(double R); + explicit Sphere(const Spec&); + + // -- Overridden methods + + double R() const override { return R_; } + double a() const override { return R_; } + double b() const override { return R_; } + +private: + // -- Members + + const double R_; +}; + + +} // namespace eckit::geo::figure diff --git a/src/eckit/geo/figure/UnitSphere.cc b/src/eckit/geo/figure/UnitSphere.cc new file mode 100644 index 000000000..2fe0015db --- /dev/null +++ b/src/eckit/geo/figure/UnitSphere.cc @@ -0,0 +1,22 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/figure/UnitSphere.h" + + +namespace eckit::geo::figure { + + +static const ConcreteBuilderT1 REGISTER("unit-sphere"); + + +} // namespace eckit::geo::figure diff --git a/src/eckit/geo/figure/UnitSphere.h b/src/eckit/geo/figure/UnitSphere.h new file mode 100644 index 000000000..5d6ba19fd --- /dev/null +++ b/src/eckit/geo/figure/UnitSphere.h @@ -0,0 +1,27 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/figure/Sphere.h" + + +namespace eckit::geo::figure { + + +struct UnitSphere final : public Sphere { + explicit UnitSphere() : Sphere(1.) {} + explicit UnitSphere(const Spec&) : UnitSphere() {} +}; + + +} // namespace eckit::geo::figure diff --git a/src/eckit/geo/geometry/OblateSpheroid.cc b/src/eckit/geo/geometry/OblateSpheroid.cc new file mode 100644 index 000000000..a94d8fddc --- /dev/null +++ b/src/eckit/geo/geometry/OblateSpheroid.cc @@ -0,0 +1,58 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/geometry/OblateSpheroid.h" + +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/Point3.h" +#include "eckit/geo/PointLonLat.h" +#include "eckit/geo/util.h" + + +namespace eckit::geo::geometry { + + +double OblateSpheroid::eccentricity(double a, double b) { + ASSERT(0. < b && b <= a); + return std::sqrt(1. - b * b / (a * a)); +} + + +double OblateSpheroid::flattening(double a, double b) { + ASSERT(0. < b && b <= a); + return (a - b) / a; +} + + +Point3 OblateSpheroid::convertSphericalToCartesian(double a, double b, const PointLonLat& P, double height) { + ASSERT(0. < b && 0. < a); + + // See https://en.wikipedia.org/wiki/Reference_ellipsoid#Coordinates + // numerical conditioning for both ϕ (poles) and λ (Greenwich/Date Line) + + const auto Q = PointLonLat::make(P.lon, P.lat, -180.); + const auto lambda = util::DEGREE_TO_RADIAN * Q.lon; + const auto phi = util::DEGREE_TO_RADIAN * Q.lat; + + const auto sp = std::sin(phi); + const auto cp = std::sqrt(1. - sp * sp); + const auto sl = std::abs(Q.lon) < 180. ? std::sin(lambda) : 0.; + const auto cl = std::abs(Q.lon) > 90. ? std::cos(lambda) : std::sqrt(1. - sl * sl); + + const auto N_phi = a * a / std::sqrt(a * a * cp * cp + b * b * sp * sp); + + return {(N_phi + height) * cp * cl, (N_phi + height) * cp * sl, (N_phi * (b * b) / (a * a) + height) * sp}; +} + + +} // namespace eckit::geo::geometry diff --git a/src/eckit/geo/geometry/OblateSpheroid.h b/src/eckit/geo/geometry/OblateSpheroid.h new file mode 100644 index 000000000..3261dcd3a --- /dev/null +++ b/src/eckit/geo/geometry/OblateSpheroid.h @@ -0,0 +1,39 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + + +namespace eckit::geo { +class Point3; +class PointLonLat; +} // namespace eckit::geo + + +namespace eckit::geo::geometry { + + +struct OblateSpheroid { + /// Elliptic eccentricity + static double eccentricity(double a, double b); + + /// Flattening + static double flattening(double a, double b); + + /// Surface area [m ** 2] + static double area(double a, double b); + + /// Convert geocentric coordinates to Cartesian + static Point3 convertSphericalToCartesian(double a, double b, const PointLonLat&, double height = 0.); +}; + + +} // namespace eckit::geo::geometry diff --git a/src/eckit/geo/geometry/Sphere.cc b/src/eckit/geo/geometry/Sphere.cc new file mode 100644 index 000000000..919fa4d59 --- /dev/null +++ b/src/eckit/geo/geometry/Sphere.cc @@ -0,0 +1,185 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/geometry/Sphere.h" + +#include +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/GreatCircle.h" +#include "eckit/geo/Point3.h" +#include "eckit/geo/PointLonLat.h" +#include "eckit/geo/area/BoundingBox.h" +#include "eckit/geo/util.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::geometry { + + +using types::is_approximately_equal; + + +double Sphere::centralAngle(const PointLonLat& P1, const PointLonLat& P2) { + /* + * Δσ = atan( ((cos(ϕ2) * sin(Δλ))^2 + (cos(ϕ1) * sin(ϕ2) - sin(ϕ1) * cos(ϕ2) * cos(Δλ))^2) / + * (sin(ϕ1) * sin(ϕ2) + cos(ϕ1) * cos(ϕ2) * cos(Δλ)) ) + * + * @article{doi:10.1179/sre.1975.23.176.88, + * author = {T. Vincenty}, + * title = {Direct and Inverse Solutions of Geodesics on the Ellipsoid With Application of Nested Equations}, + * journal = {Survey Review}, + * volume = {23}, + * number = {176}, + * pages = {88-93}, + * year = {1975}, + * doi = {10.1179/sre.1975.23.176.88} + * } + */ + + const auto Q1 = PointLonLat::make(P1.lon, P1.lat, -180.); + const auto Q2 = PointLonLat::make(P2.lon, P2.lat, -180.); + + const auto phi1 = util::DEGREE_TO_RADIAN * Q1.lat; + const auto phi2 = util::DEGREE_TO_RADIAN * Q2.lat; + const auto lambda = util::DEGREE_TO_RADIAN * PointLonLat::normalise_angle_to_minimum(Q1.lon - Q2.lon, -180.); + + const auto cp1 = std::cos(phi1); + const auto sp1 = std::sin(phi1); + const auto cp2 = std::cos(phi2); + const auto sp2 = std::sin(phi2); + const auto cl = std::cos(lambda); + const auto sl = std::sin(lambda); + + auto squared = [](double x) { return x * x; }; + + const auto angle + = std::atan2(std::sqrt(squared(cp2 * sl) + squared(cp1 * sp2 - sp1 * cp2 * cl)), sp1 * sp2 + cp1 * cp2 * cl); + + if (is_approximately_equal(angle, 0.)) { + return 0.; + } + + ASSERT(angle > 0.); + return angle; +} + + +double Sphere::centralAngle(double radius, const Point3& P1, const Point3& P2) { + ASSERT(radius > 0.); + + // Δσ = 2 * asin( chord / 2 ) + + const auto d2 = Point3::distance2(P1, P2); + if (is_approximately_equal(d2, 0.)) { + return 0.; + } + + const auto chord = std::sqrt(d2) / radius; + const auto angle = std::asin(chord * 0.5) * 2.; + + return angle; +} + + +double Sphere::distance(double radius, const PointLonLat& P1, const PointLonLat& P2) { + return radius * centralAngle(P1, P2); +} + + +double Sphere::distance(double radius, const Point3& P1, const Point3& P2) { + return radius * centralAngle(radius, P1, P2); +} + + +double Sphere::area(double radius) { + ASSERT(radius > 0.); + return 4. * M_PI * radius * radius; +} + + +double Sphere::area(double radius, const area::BoundingBox& bbox) { + ASSERT(radius > 0.); + + // Set longitude and latitude fractions + auto lonf = bbox.periodic() ? 1. : (bbox.east - bbox.west) / PointLonLat::FULL_ANGLE; + ASSERT(0. <= lonf && lonf <= 1.); + + auto sn = std::sin(util::DEGREE_TO_RADIAN * bbox.north); + auto ss = std::sin(util::DEGREE_TO_RADIAN * bbox.south); + auto latf = 0.5 * (sn - ss); + + // Calculate area + return area(radius) * latf * lonf; +} + + +double Sphere::greatCircleLatitudeGivenLongitude(const PointLonLat& P1, const PointLonLat& P2, double Clon) { + GreatCircle gc(P1, P2); + auto lat = gc.latitude(Clon); + return lat.size() == 1 ? lat[0] : std::numeric_limits::signaling_NaN(); +} + + +void Sphere::greatCircleLongitudeGivenLatitude(const PointLonLat& P1, const PointLonLat& P2, double Clat, double& Clon1, + double& Clon2) { + GreatCircle gc(P1, P2); + auto lon = gc.longitude(Clat); + + Clon1 = lon.size() > 0 ? lon[0] : std::numeric_limits::signaling_NaN(); + Clon2 = lon.size() > 1 ? lon[1] : std::numeric_limits::signaling_NaN(); +} + + +Point3 Sphere::convertSphericalToCartesian(double radius, const PointLonLat& P, double height) { + ASSERT(radius > 0.); + + /* + * See https://en.wikipedia.org/wiki/Reference_ellipsoid#Coordinates + * numerical conditioning for both ϕ (poles) and λ (Greenwich/Date Line). + * + * cos φ = sqrt( 1 - sin^2 φ) is better conditioned than explicit cos φ, and + * coupled with λ in [-180°, 180°[ the accuracy of the trigonometric + * functions is the same (before converting/multiplying its angle argument + * to radian) and explicitly chosing -180° over 180° for longitude. + * + * These conditionings combined project accurately to sphere poles and quadrants. + */ + + const auto Q = PointLonLat::make(P.lon, P.lat, -180.); + const auto lambda = util::DEGREE_TO_RADIAN * Q.lon; + const auto phi = util::DEGREE_TO_RADIAN * Q.lat; + + const auto sp = std::sin(phi); + const auto cp = std::sqrt(1. - sp * sp); + const auto sl = std::abs(Q.lon) < 180. ? std::sin(lambda) : 0.; + const auto cl = std::abs(Q.lon) > 90. ? std::cos(lambda) : std::sqrt(1. - sl * sl); + + return {(radius + height) * cp * cl, (radius + height) * cp * sl, (radius + height) * sp}; +} + + +PointLonLat Sphere::convertCartesianToSpherical(double radius, const Point3& A) { + ASSERT(radius > 0.); + + // numerical conditioning for both z (poles) and y + + const auto x = A[0]; + const auto y = is_approximately_equal(A[1], 0.) ? 0. : A[1]; + const auto z = std::min(radius, std::max(-radius, A[2])) / radius; + + return {util::RADIAN_TO_DEGREE * std::atan2(y, x), util::RADIAN_TO_DEGREE * std::asin(z)}; +} + + +} // namespace eckit::geo::geometry diff --git a/src/eckit/geo/geometry/Sphere.h b/src/eckit/geo/geometry/Sphere.h new file mode 100644 index 000000000..2668afd18 --- /dev/null +++ b/src/eckit/geo/geometry/Sphere.h @@ -0,0 +1,62 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + + +namespace eckit::geo { +class Point3; +class PointLonLat; +namespace area { +class BoundingBox; +} +} // namespace eckit::geo + + +namespace eckit::geo::geometry { + + +/// Spherical geometry +struct Sphere { + /// Great-circle central angle between two points [radian] + static double centralAngle(const PointLonLat&, const PointLonLat&); + + /// Great-circle central angle between two points (Cartesian coordinates) [m] + static double centralAngle(double radius, const Point3&, const Point3&); + + /// Great-circle distance between two points [m] + static double distance(double radius, const PointLonLat&, const PointLonLat&); + + /// Great-circle distance between two points (Cartesian coordinates) [m] + static double distance(double radius, const Point3&, const Point3&); + + /// Surface area [m ** 2] + static double area(double radius); + + /// Surface area between parallels and meridians [m ** 2] + static double area(double radius, const area::BoundingBox&); + + /// Great-circle intermediate latitude provided two circle points and intermediate longitude [degree] + static double greatCircleLatitudeGivenLongitude(const PointLonLat&, const PointLonLat&, double lon); + + /// Great-circle intermediate longitude(s) provided two circle points and intermediate latitude [degree] + static void greatCircleLongitudeGivenLatitude(const PointLonLat&, const PointLonLat&, double lat, double& lon1, + double& lon2); + + /// Convert spherical to Cartesian coordinates + static Point3 convertSphericalToCartesian(double radius, const PointLonLat&, double height = 0.); + + /// Convert Cartesian to spherical coordinates + static PointLonLat convertCartesianToSpherical(double radius, const Point3&); +}; + + +} // namespace eckit::geo::geometry diff --git a/src/eckit/geo/geometry/SphereT.h b/src/eckit/geo/geometry/SphereT.h new file mode 100644 index 000000000..828012517 --- /dev/null +++ b/src/eckit/geo/geometry/SphereT.h @@ -0,0 +1,79 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/Point3.h" +#include "eckit/geo/PointLonLat.h" +#include "eckit/geo/geometry/Sphere.h" + + +namespace eckit::geo::area { +class BoundingBox; +} + + +namespace eckit::geo::geometry { + + +/// Sphere parametrised with a geodetic datum +template +struct SphereT { + + /// Sphere radius in metres + inline static double radius() { return DATUM::radius(); } + + /// Great-circle central angle between two points [radian] + inline static double centralAngle(const PointLonLat& A, const PointLonLat& B) { return Sphere::centralAngle(A, B); } + + /// Great-circle central angle between two points (Cartesian coordinates) in radians + inline static double centralAngle(const Point3& A, const Point3& B) { + return Sphere::centralAngle(DATUM::radius(), A, B); + } + + /// Great-circle distance between two points [m] + inline static double distance(const PointLonLat& A, const PointLonLat& B) { + return Sphere::distance(DATUM::radius(), A, B); + } + + /// Great-circle distance between two points (Cartesian coordinates) [m] + inline static double distance(const Point3& A, const Point3& B) { return Sphere::distance(DATUM::radius(), A, B); } + + /// Surface area [m ** 2] + inline static double area() { return Sphere::area(DATUM::radius()); } + + /// Surface area between parallels and meridians [m ** 2] + inline static double area(const area::BoundingBox& bbox) { return Sphere::area(DATUM::radius(), bbox); } + + /// Great-circle intermediate latitude provided two circle points and intermediate longitude [degree] + inline static double greatCircleLatitudeGivenLongitude(const PointLonLat& A, const PointLonLat& B, double lon) { + return Sphere::greatCircleLatitudeGivenLongitude(A, B, lon); + } + + /// Great-circle intermediate longitude(s) provided two circle points and intermediate latitude [degree] + inline static void greatCircleLongitudeGivenLatitude(const PointLonLat& A, const PointLonLat& B, double lat, + double& lon1, double& lon2) { + return Sphere::greatCircleLongitudeGivenLatitude(A, B, lat, lon1, lon2); + } + + /// Convert spherical to Cartesian coordinates + inline static Point3 convertSphericalToCartesian(const PointLonLat& P, double height = 0.) { + return Sphere::convertSphericalToCartesian(DATUM::radius(), P, height); + } + + /// Convert Cartesian to spherical coordinates + inline static PointLonLat convertCartesianToSpherical(const Point3& P) { + return Sphere::convertCartesianToSpherical(DATUM::radius(), P); + } +}; + + +} // namespace eckit::geo::geometry diff --git a/src/eckit/geo/geometry/UnitSphere.h b/src/eckit/geo/geometry/UnitSphere.h new file mode 100644 index 000000000..8acb2a1a0 --- /dev/null +++ b/src/eckit/geo/geometry/UnitSphere.h @@ -0,0 +1,30 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/geometry/SphereT.h" + + +namespace eckit::geo::geometry { + + +/// Definition of a unit datum +struct DatumUnit { + static constexpr double radius() { return 1.; } +}; + + +/// Definition of a unit sphere +using UnitSphere = SphereT; + + +} // namespace eckit::geo::geometry diff --git a/src/eckit/geo/grid/HEALPix.cc b/src/eckit/geo/grid/HEALPix.cc new file mode 100644 index 000000000..642f08a7e --- /dev/null +++ b/src/eckit/geo/grid/HEALPix.cc @@ -0,0 +1,375 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/grid/HEALPix.h" + +#include +#include +#include +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/iterator/Reduced.h" +#include "eckit/geo/iterator/Unstructured.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/geo/util.h" +#include "eckit/utils/Translator.h" + + +namespace eckit::geo::grid { + + +namespace { + + +struct CodecFijNest { + static constexpr uint64_t MASKS[] = {0x00000000ffffffff, 0x0000ffff0000ffff, 0x00ff00ff00ff00ff, + 0x0f0f0f0f0f0f0f0f, 0x3333333333333333, 0x5555555555555555}; + + inline static int nest_encode_bits(int n) { + auto b = static_cast(n) & MASKS[0]; + b = (b ^ (b << 16)) & MASKS[1]; + b = (b ^ (b << 8)) & MASKS[2]; + b = (b ^ (b << 4)) & MASKS[3]; + b = (b ^ (b << 2)) & MASKS[4]; + b = (b ^ (b << 1)) & MASKS[5]; + return static_cast(b); + } + + inline static int nest_decode_bits(int n) { + auto b = static_cast(n) & MASKS[5]; + b = (b ^ (b >> 1)) & MASKS[4]; + b = (b ^ (b >> 2)) & MASKS[3]; + b = (b ^ (b >> 4)) & MASKS[2]; + b = (b ^ (b >> 8)) & MASKS[1]; + b = (b ^ (b >> 16)) & MASKS[0]; + return static_cast(b); + } + + static std::tuple nest_to_fij(int n, int k) { + ASSERT(0 <= n); + auto f = n >> (2 * k); // f = n / (Nside * Nside) + n &= (1 << (2 * k)) - 1; // n = n % (Nside * Nside) + auto i = nest_decode_bits(n); + auto j = nest_decode_bits(n >> 1); + return {f, i, j}; + } + + static int fij_to_nest(int f, int i, int j, int k) { + return (f << (2 * k)) + nest_encode_bits(i) + (nest_encode_bits(j) << 1); + } +}; + + +inline int sqrt(int n) { + return static_cast(std::sqrt(static_cast(n) + 0.5)); +} + + +// for division result within [0; 3] +inline int div_03(int a, int b) { + int t = (a >= (b << 1)) ? 1 : 0; + a -= t * (b << 1); + return (t << 1) + (a >= b ? 1 : 0); +} + + +inline bool is_power_of_2(int n) { + return std::bitset(n).count() == 1; +} + + +inline int pll(int f) { + constexpr int __pll[] = {1, 3, 5, 7, 0, 2, 4, 6, 1, 3, 5, 7}; + return __pll[f]; +} + + +class Reorder { +public: + explicit Reorder(int Nside) : + Nside_(Nside), + Npix_(size()), + Ncap_((Nside * (Nside - 1)) << 1), + k_(is_power_of_2(Nside_) ? static_cast(std::log2(Nside)) : -1) { + ASSERT(0 <= k_); // (specific to nested ordering) + ASSERT(0 < Nside_); + } + + int size() const { return 12 * Nside_ * Nside_; } + int nside() const { return Nside_; } + + int ring_to_nest(int r) const { + auto to_nest = [&](int f, //!< base pixel index + int ring, //!< 1-based ring number + int Nring, //!< number of pixels in ring + int phi, //!< index in longitude + int shift //!< if ring's first pixel is not at phi=0 + ) -> int { + int r = ((2 + (f >> 2)) << k_) - ring - 1; + int p = 2 * phi - pll(f) * Nring - shift - 1; + if (p >= 2 * Nside_) { + p -= 8 * Nside_; + } + + int i = (r + p) >> 1; + int j = (r - p) >> 1; + + ASSERT(f < 12 && i < Nside_ && j < Nside_); + return CodecFijNest::fij_to_nest(f, i, j, k_); + }; + + if (r < Ncap_) { + // North polar cap + int Nring = (1 + sqrt(2 * r + 1)) >> 1; + int phi = 1 + r - 2 * Nring * (Nring - 1); + int r = Nring; + int f = div_03(phi - 1, Nring); + + return to_nest(f, r, Nring, phi, 0); + } + + if (Npix_ - Ncap_ <= r) { + // South polar cap + int Nring = (1 + sqrt(2 * Npix_ - 2 * r - 1)) >> 1; + int phi = 1 + r + 2 * Nring * (Nring - 1) + 4 * Nring - Npix_; + int ring = 4 * Nside_ - Nring; // (from South pole) + int f = div_03(phi - 1, Nring) + 8; + + return to_nest(f, ring, Nring, phi, 0); + } + + // Equatorial belt + int ip = r - Ncap_; + int tmp = ip >> (k_ + 2); + + int Nring = Nside_; + int phi = ip - tmp * 4 * Nside_ + 1; + int ring = tmp + Nside_; + + int ifm = 1 + ((phi - 1 - ((1 + tmp) >> 1)) >> k_); + int ifp = 1 + ((phi - 1 - ((1 - tmp + 2 * Nside_) >> 1)) >> k_); + int f = (ifp == ifm) ? (ifp | 4) : ((ifp < ifm) ? ifp : (ifm + 8)); + + return to_nest(f, ring, Nring, phi, ring & 1); + } + + int nest_to_ring(int n) const { + auto [f, i, j] = CodecFijNest::nest_to_fij(n, k_); + ASSERT(f < 12 && i < Nside_ && j < Nside_); + + auto to_ring_local = [&](int f, int i, int j, + int Nring, //!< number of pixels in ring + int shift //!< if ring's first pixel is/is not at phi=0 + ) -> int { + Nring >>= 2; + int r = (pll(f) * Nring + i - j + 1 + shift) / 2 - 1; + ASSERT(r < 4 * Nring); + + return r < 0 ? r + 4 * Nside_ : r; + }; + + const int ring = ((f >> 2) + 2) * Nside_ - i - j - 1; // 1-based ring number + if (ring < Nside_) { + // North polar cap + int Nring = 4 * ring; + int r0 = 2 * ring * (ring - 1); // index of first ring pixel (ring numbering) + + return r0 + to_ring_local(f, i, j, Nring, 0); + } + + if (ring < 3 * Nside_) { + // South polar cap + int Nring = 4 * Nside_; + int r0 = Ncap_ + (ring - Nside_) * Nring; // index of first ring pixel (ring numbering) + int shift = (ring - Nside_) & 1; + + return r0 + to_ring_local(f, i, j, Nring, shift); + } + + // Equatorial belt + int N = 4 * Nside_ - ring; + int Nring = 4 * N; + int r0 = Npix_ - 2 * N * (N + 1); // index of first ring pixel (ring numbering) + + return r0 + to_ring_local(f, i, j, Nring, 0); + } + +private: + const int Nside_; // up to 2^13 + const int Npix_; + const int Ncap_; + const int k_; +}; + + +} // unnamed namespace + + +HEALPix::HEALPix(const Spec& spec) : + HEALPix(spec.get_unsigned("Nside"), [](const std::string& str) { + return str == "ring" ? Ordering::healpix_ring + : str == "nested" ? Ordering::healpix_nested + : throw AssertionFailed("HEALPix: supported orderings: ring, nested", Here()); + }(spec.get_string("ordering", "ring"))) {} + + +HEALPix::HEALPix(size_t Nside, Ordering ordering) : Reduced(area::BoundingBox{}), Nside_(Nside), ordering_(ordering) { + ASSERT(Nside_ > 0); + ASSERT_MSG(ordering == Ordering::healpix_ring || ordering == Ordering::healpix_nested, + "HEALPix: supported orderings: ring, nested"); + + if (ordering_ == Ordering::healpix_nested) { + ASSERT(is_power_of_2(Nside)); + } +} + + +Renumber HEALPix::reorder(Ordering ordering) const { + ASSERT_MSG(ordering == Ordering::healpix_ring || ordering == Ordering::healpix_nested, + "HEALPix: supported orderings: ring, nested"); + + if (ordering == ordering_) { + return Grid::no_reorder(size()); + } + + const Reorder reorder(static_cast(Nside_)); + const auto N = static_cast(size()); + + Renumber ren(N); + for (int i = 0; i < N; ++i) { + ren[i] = ordering == Ordering::healpix_ring ? reorder.nest_to_ring(i) : reorder.ring_to_nest(i); + } + return ren; +} + + +Grid::iterator HEALPix::cbegin() const { + return ordering_ == Ordering::healpix_ring ? iterator{new geo::iterator::Reduced(*this, 0)} + : iterator{new geo::iterator::Unstructured(*this, 0, to_points())}; +} + + +Grid::iterator HEALPix::cend() const { + return ordering_ == Ordering::healpix_ring ? iterator{new geo::iterator::Reduced(*this, size())} + : iterator{new geo::iterator::Unstructured(*this)}; +} + + +size_t HEALPix::ni(size_t j) const { + ASSERT(j < nj()); + return j < Nside_ ? 4 * (j + 1) : j < 3 * Nside_ ? 4 * Nside_ : ni(nj() - 1 - j); +} + + +size_t HEALPix::nj() const { + return 4 * Nside_ - 1; +} + + +Spec* HEALPix::spec(const std::string& name) { + ASSERT(name.size() > 1 && (name[0] == 'h' || name[0] == 'H')); + + auto Nside = Translator{}(name.substr(1)); + return new spec::Custom({{"type", "HEALPix"}, {"Nside", Nside}, {"ordering", "ring"}}); +} + + +size_t HEALPix::size() const { + return 12 * Nside_ * Nside_; +} + + +std::vector HEALPix::to_points() const { + const auto points = Reduced::to_points(); + + if (ordering_ == Ordering::healpix_ring) { + return points; + } + + std::vector points_nested; + points_nested.reserve(size()); + + const Reorder reorder(static_cast(Nside_)); + for (size_t i = 0; i < size(); ++i) { + points_nested.emplace_back(std::get(points[reorder.nest_to_ring(static_cast(i))])); + } + + return points_nested; +} + + +std::pair, std::vector> HEALPix::to_latlon() const { + std::pair, std::vector> latlon; + latlon.first.reserve(size()); + latlon.second.reserve(size()); + + for (const auto& p : to_points()) { + const auto& q = std::get(p); + latlon.first.push_back(q.lat); + latlon.second.push_back(q.lon); + } + + return latlon; +} + + +const std::vector& HEALPix::latitudes() const { + const auto Nj = nj(); + + if (latitudes_.empty()) { + latitudes_.resize(Nj); + + auto i = latitudes_.begin(); + auto j = latitudes_.rbegin(); + for (size_t ring = 1; ring < 2 * Nside_; ++ring, ++i, ++j) { + const auto f = ring < Nside_ + ? 1. - static_cast(ring * ring) / (3 * static_cast(Nside_ * Nside_)) + : 4. / 3. - 2 * static_cast(ring) / (3 * static_cast(Nside_)); + + *i = 90. - util::RADIAN_TO_DEGREE * std::acos(f); + *j = -*i; + } + *i = 0.; + } + + ASSERT(latitudes_.size() == Nj); + return latitudes_; +} + + +std::vector HEALPix::longitudes(size_t j) const { + const auto Ni = ni(j); + const auto step = 360. / static_cast(Ni); + const auto start = j < Nside_ || 3 * Nside_ - 1 < j || static_cast((j + Nside_) % 2) ? step / 2. : 0.; + + std::vector lons(Ni); + std::generate_n(lons.begin(), Ni, + [start, step, n = 0ULL]() mutable { return start + static_cast(n++) * step; }); + + return lons; +} + + +void HEALPix::fill_spec(spec::Custom& custom) const { + custom.set("grid", "H" + std::to_string(Nside_)); + custom.set("ordering", ordering_ == Ordering::healpix_ring ? "ring" : "nested"); +} + + +static const GridRegisterType GRIDTYPE1("HEALPix"); +static const GridRegisterType GRIDTYPE2("healpix"); +static const GridRegisterName GRIDNAME("[hH][1-9][0-9]*"); + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/HEALPix.h b/src/eckit/geo/grid/HEALPix.h new file mode 100644 index 000000000..e0508c262 --- /dev/null +++ b/src/eckit/geo/grid/HEALPix.h @@ -0,0 +1,74 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/grid/Reduced.h" + + +namespace eckit::geo::grid { + + +class HEALPix final : public Reduced { +public: + // -- Constructors + + explicit HEALPix(const Spec&); + explicit HEALPix(size_t Nside, Ordering = Ordering::healpix_ring); + + // -- Methods + + size_t Nside() const { return Nside_; } + + // -- Overridden methods + + iterator cbegin() const override; + iterator cend() const override; + + size_t size() const override; + + size_t ni(size_t j) const override; + size_t nj() const override; + + std::vector to_points() const override; + std::pair, std::vector> to_latlon() const override; + + Ordering ordering() const override { return ordering_; } + Renumber reorder(Ordering) const override; + [[nodiscard]] Grid* make_grid_reordered(Ordering ordering) const override { return new HEALPix(Nside_, ordering); } + + // -- Class members + + [[nodiscard]] static Spec* spec(const std::string& name); + +private: + // -- Members + + const size_t Nside_; + const Ordering ordering_; + + mutable std::vector latitudes_; + + // -- Overridden methods + + bool includesNorthPole() const override { return true; } + bool includesSouthPole() const override { return true; } + bool isPeriodicWestEast() const override { return true; } + + void fill_spec(spec::Custom&) const override; + + const std::vector& latitudes() const override; + std::vector longitudes(size_t i) const override; +}; + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/ORCA.cc b/src/eckit/geo/grid/ORCA.cc new file mode 100644 index 000000000..c73628754 --- /dev/null +++ b/src/eckit/geo/grid/ORCA.cc @@ -0,0 +1,334 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/grid/ORCA.h" + +#include + +#include "eckit/codec/codec.h" +#include "eckit/eckit_config.h" +#include "eckit/exception/Exceptions.h" +#include "eckit/filesystem/PathName.h" +#include "eckit/geo/Cache.h" +#include "eckit/geo/LibEcKitGeo.h" +#include "eckit/geo/Spec.h" +#include "eckit/geo/area/BoundingBox.h" +#include "eckit/geo/iterator/Unstructured.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/geo/util/mutex.h" +#include "eckit/io/Length.h" +#include "eckit/log/Bytes.h" +#include "eckit/log/Timer.h" +#include "eckit/types/FloatCompare.h" +#include "eckit/utils/ByteSwap.h" +#include "eckit/utils/MD5.h" + +#if eckit_HAVE_CURL +#include "eckit/io/URLHandle.h" +#endif + + +namespace eckit::geo::grid { + + +namespace { + + +ORCA::Arrangement arrangement_from_string(const std::string& str) { + return str == "F" ? ORCA::Arrangement::F + : str == "T" ? ORCA::Arrangement::T + : str == "U" ? ORCA::Arrangement::U + : str == "V" ? ORCA::Arrangement::V + : str == "W" ? ORCA::Arrangement::W + : throw AssertionFailed("ORCA::Arrangement", Here()); +} + + +std::string arrangement_to_string(ORCA::Arrangement a) { + return a == ORCA::Arrangement::F ? "F" + : a == ORCA::Arrangement::T ? "T" + : a == ORCA::Arrangement::U ? "U" + : a == ORCA::Arrangement::V ? "V" + : a == ORCA::Arrangement::W ? "W" + : throw AssertionFailed("ORCA::Arrangement", Here()); +} + + +util::recursive_mutex MUTEX; + + +class lock_type { + util::lock_guard lock_guard_{MUTEX}; +}; + + +CacheT CACHE; + + +PathName orca_path(const PathName& path, const std::string& url) { + // control concurrent download/access + lock_type lock; + +#if eckit_HAVE_CURL // for eckit::URLHandle + if (!path.exists() && LibEcKitGeo::caching()) { + auto dir = path.dirName(); + dir.mkdir(); + ASSERT(dir.exists()); + + auto tmp = path + ".download"; + + Timer timer; + Log::info() << "ORCA: downloading '" << url << "' to '" << path << "'..." << std::endl; + + Length length = 0; + try { + length = URLHandle(url).saveInto(tmp); + } + catch (...) { + length = 0; + } + + if (length <= 0) { + if (tmp.exists()) { + tmp.unlink(true); + } + + throw UserError("ORCA: download error", Here()); + } + + PathName::rename(tmp, path); + Log::info() << "ORCA: download of " << Bytes(static_cast(length)) << " took " << timer.elapsed() + << " s." << std::endl; + } +#endif + + ASSERT_MSG(path.exists(), "ORCA: file '" + path + "' not found"); + return path; +} + + +const ORCA::ORCARecord& orca_record(const PathName& p, const Spec& spec) { + // control concurrent reads/writes + lock_type lock; + + if (CACHE.contains(p)) { + return CACHE[p]; + } + + // read and check against metadata (if present) + auto& record = CACHE[p]; + record.read(p); + record.check(spec); + + return record; +} + + +} // namespace + + +ORCA::ORCA(const Spec& spec) : + Regular(spec), + name_(spec.get_string("orca_name")), + uid_(spec.get_string("orca_uid")), + arrangement_(arrangement_from_string(spec.get_string("orca_arrangement"))), + record_(orca_record( + orca_path(spec.get_string("path", LibEcKitGeo::cacheDir() + "/eckit/geo/grid/orca/" + uid_ + ".atlas"), + spec.get_string("url_prefix", "") + spec.get_string("url")), + spec)) {} + + +ORCA::ORCA(uid_t uid) : ORCA(*std::unique_ptr(GridFactory::make_spec(spec::Custom({{"uid", uid}})))) {} + + +std::string ORCA::arrangement() const { + return arrangement_to_string(arrangement_); +} + + +Grid::uid_t ORCA::ORCARecord::calculate_uid(Arrangement arrangement) const { + MD5 hash; + hash.add(arrangement_to_string(arrangement)); + + auto sized = static_cast(longitudes_.size() * sizeof(double)); + + if constexpr (eckit_LITTLE_ENDIAN) { + hash.add(latitudes_.data(), sized); + hash.add(longitudes_.data(), sized); + } + else { + auto lonsw = longitudes_; + auto latsw = latitudes_; + eckit::byteswap(latsw.data(), latsw.size()); + eckit::byteswap(lonsw.data(), lonsw.size()); + hash.add(latsw.data(), sized); + hash.add(lonsw.data(), sized); + } + + auto d = hash.digest(); + ASSERT(d.length() == 32); + + return {d}; +} + + +ORCA::ORCARecord::bytes_t ORCA::ORCARecord::footprint() const { + return sizeof(dimensions_.front()) * dimensions_.size() + sizeof(halo_.front()) * halo_.size() + + sizeof(pivot_.front()) * pivot_.size() + sizeof(longitudes_.front()) * longitudes_.size() + + sizeof(latitudes_.front()) * latitudes_.size() + sizeof(flags_.front()) * flags_.size(); +} + + +size_t ORCA::ORCARecord::ni() const { + ASSERT(0 <= dimensions_[0]); + return static_cast(dimensions_[0]); +} + + +size_t ORCA::ORCARecord::nj() const { + ASSERT(0 <= dimensions_[1]); + return static_cast(dimensions_[1]); +} + + +void ORCA::ORCARecord::read(const PathName& p) { + codec::RecordReader reader(p); + + int version = -1; + reader.read("version", version).wait(); + + if (version == 0) { + reader.read("dimensions", dimensions_); + reader.read("pivot", pivot_); // different order from writer + reader.read("halo", halo_); //... + reader.read("longitude", longitudes_); + reader.read("latitude", latitudes_); + reader.read("flags", flags_); + reader.wait(); + return; + } + + throw SeriousBug("ORCA::read: unsupported version", Here()); +} + + +void ORCA::ORCARecord::check(const Spec& spec) const { + if (spec.get_bool("orca_uid_check", false)) { + auto uid = spec.get_string("orca_uid"); + ASSERT(uid.length() == 32); + ASSERT(uid == calculate_uid(arrangement_from_string(spec.get_string("orca_arrangement")))); + } + + if (std::vector d; spec.get("dimensions", d)) { + ASSERT(d.size() == 2); + ASSERT(d[0] == dimensions_[0] && d[1] == dimensions_[1]); + } + + if (std::vector h; spec.get("halo", h)) { + ASSERT(h.size() == 4); + ASSERT(h[0] == halo_[0] && h[1] == halo_[1] && h[2] == halo_[2] && h[3] == halo_[3]); + } + + if (std::vector p; spec.get("pivot", p)) { + ASSERT(p.size() == 2); + ASSERT(types::is_approximately_equal(p[0], pivot_[0])); + ASSERT(types::is_approximately_equal(p[1], pivot_[1])); + } + + auto n = static_cast(dimensions_[0] * dimensions_[1]); + ASSERT(n > 0); + ASSERT(n == longitudes_.size()); + ASSERT(n == latitudes_.size()); + ASSERT(n == flags_.size()); +} + + +size_t ORCA::ORCARecord::write(const PathName& p, const std::string& compression) { + codec::RecordWriter record; + + codec::ArrayShape shape{static_cast(dimensions_[0]), static_cast(dimensions_[1])}; + + record.compression(compression); + record.set("version", 0); + record.set("dimensions", dimensions_); + record.set("halo", halo_); + record.set("pivot", pivot_); + record.set("longitude", codec::ArrayReference(longitudes_.data(), shape)); + record.set("latitude", codec::ArrayReference(latitudes_.data(), shape)); + record.set("flags", codec::ArrayReference(flags_.data(), shape)); + + return record.write(p); +} + + +Grid::iterator ORCA::cbegin() const { + return iterator{new geo::iterator::Unstructured(*this, 0, record_.longitudes_, record_.latitudes_)}; +} + + +Grid::iterator ORCA::cend() const { + return iterator{new geo::iterator::Unstructured(*this)}; +} + + +Grid::uid_t ORCA::calculate_uid() const { + MD5 hash(arrangement_to_string(arrangement_)); + + if (const auto len = static_cast(size() * sizeof(double)); eckit_LITTLE_ENDIAN) { + hash.add(record_.latitudes_.data(), len); + hash.add(record_.longitudes_.data(), len); + } + else { + auto ll = to_latlon(); + byteswap(ll.first.data(), size()); + byteswap(ll.second.data(), size()); + + hash.add(ll.first.data(), len); + hash.add(ll.second.data(), len); + } + + return hash; +} + + +std::vector ORCA::to_points() const { + std::vector p; + p.reserve(size()); + + for (size_t i = 0; i < size(); ++i) { + p.emplace_back(PointLonLat{record_.longitudes_[i], record_.latitudes_[i]}); + } + return p; +} + + +std::pair, std::vector> ORCA::to_latlon() const { + return {record_.latitudes_, record_.longitudes_}; +} + + +Spec* ORCA::spec(const std::string& name) { + return SpecByUID::instance().get(name).spec(); +} + + +void ORCA::fill_spec(spec::Custom& custom) const { + custom.set("type", "ORCA"); + custom.set("uid", uid_); +} + + +static const GridRegisterType GRIDTYPE("ORCA"); +static const GridRegisterName GRIDNAME(GridRegisterName::uid_pattern); + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/ORCA.h b/src/eckit/geo/grid/ORCA.h new file mode 100644 index 000000000..f35680649 --- /dev/null +++ b/src/eckit/geo/grid/ORCA.h @@ -0,0 +1,111 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include +#include + +#include "eckit/geo/grid/Regular.h" + + +namespace eckit { +class PathName; +} + + +namespace eckit::geo::grid { + + +class ORCA final : public Regular { +public: + // -- Types + + enum Arrangement + { + F, + T, + U, + V, + W, + }; + + struct ORCARecord { + explicit ORCARecord() = default; + + void read(const PathName&); + void check(const Spec&) const; + size_t write(const PathName&, const std::string& compression = "none"); + uid_t calculate_uid(Arrangement) const; + + using bytes_t = decltype(sizeof(int)); + bytes_t footprint() const; + + size_t ni() const; + size_t nj() const; + + std::array dimensions_ = {-1, -1}; + std::array halo_ = {-1, -1, -1, -1}; + std::array pivot_ = {-1, -1}; + + std::vector longitudes_; + std::vector latitudes_; + std::vector flags_; + }; + + // -- Constructors + + explicit ORCA(const Spec&); + explicit ORCA(uid_t); + + // -- Methods + + size_t nx() const override { return record_.nj(); } + size_t ny() const override { return record_.ni(); } + + std::string name() const { return name_; } + std::string arrangement() const; + + // -- Overridden methods + + iterator cbegin() const override; + iterator cend() const override; + + uid_t calculate_uid() const override; + + bool includesNorthPole() const override { return true; } + bool includesSouthPole() const override { return true; } // FIXME: not sure this is semanticaly correct + bool isPeriodicWestEast() const override { return true; } + + std::vector to_points() const override; + std::pair, std::vector> to_latlon() const override; + + // -- Class methods + + [[nodiscard]] static Spec* spec(const std::string& name); + +private: + // -- Members + + std::string name_; + uid_t uid_; + Arrangement arrangement_; + const ORCARecord& record_; + + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; +}; + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/Reduced.cc b/src/eckit/geo/grid/Reduced.cc new file mode 100644 index 000000000..d3a7a265c --- /dev/null +++ b/src/eckit/geo/grid/Reduced.cc @@ -0,0 +1,86 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/grid/Reduced.h" + +#include "eckit/exception/Exceptions.h" + + +namespace eckit::geo::grid { + + +std::vector Reduced::to_points() const { + std::vector points; + points.reserve(size()); + + const auto& lats = latitudes(); + ASSERT(lats.size() == nj()); + + for (size_t j = 0; j < nj(); ++j) { + const auto lons = longitudes(j); + ASSERT(lons.size() == ni(j)); + + const auto lat = lats.at(j); + for (auto lon : lons) { + points.emplace_back(PointLonLat{lon, lat}); + } + } + + return points; +} + + +std::pair, std::vector> Reduced::to_latlon() const { + const auto N = size(); + + std::pair, std::vector> latlon; + auto& lat = latlon.first; + auto& lon = latlon.second; + lat.reserve(N); + lon.reserve(N); + + const auto& lats = latitudes(); + ASSERT(lats.size() == nj()); + + for (size_t j = 0; j < nj(); ++j) { + const auto lons = longitudes(j); + + lat.insert(lat.end(), lons.size(), lats.at(j)); + lon.insert(lon.end(), lons.begin(), lons.end()); + } + + ASSERT(lat.size() == N && lon.size() == N); + return latlon; +} + + +Reduced::Reduced(const area::BoundingBox& bbox, Projection* projection) : Grid(bbox, projection) {} + + +const std::vector& Reduced::niacc() const { + if (niacc_.empty()) { + niacc_.resize(1 + nj()); + niacc_.front() = 0; + + size_t j = 0; + for (auto a = niacc_.begin(), b = a + 1; b != niacc_.end(); ++j, ++a, ++b) { + *b = *a + ni(j); + } + + ASSERT(niacc_.back() == size()); + } + + return niacc_; +} + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/Reduced.h b/src/eckit/geo/grid/Reduced.h new file mode 100644 index 000000000..16f5a51b9 --- /dev/null +++ b/src/eckit/geo/grid/Reduced.h @@ -0,0 +1,65 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/Grid.h" + + +namespace eckit::geo::iterator { +class Reduced; +} + + +namespace eckit::geo::grid { + + +class Reduced : public Grid { +public: + // -- Methods + + size_t size() const override { return niacc().back(); } + + // -- Overridden methods + + std::vector to_points() const override; + std::pair, std::vector> to_latlon() const override; + +protected: + // -- Constructors + + explicit Reduced(const area::BoundingBox& = {}, Projection* = nullptr); + + // -- Methods + + const std::vector& niacc() const; + + virtual size_t ni(size_t j) const = 0; + virtual size_t nj() const = 0; + +private: + // -- Members + + mutable std::vector niacc_; + + // Methods + + virtual const std::vector& latitudes() const = 0; + virtual std::vector longitudes(size_t i) const = 0; + + // -- Friends + + friend class geo::iterator::Reduced; +}; + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/ReducedGaussian.cc b/src/eckit/geo/grid/ReducedGaussian.cc new file mode 100644 index 000000000..ca718306a --- /dev/null +++ b/src/eckit/geo/grid/ReducedGaussian.cc @@ -0,0 +1,161 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/grid/ReducedGaussian.h" + +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/iterator/Reduced.h" +#include "eckit/geo/range/GaussianLatitude.h" +#include "eckit/geo/range/RegularLongitude.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/utils/Translator.h" + + +namespace eckit::geo::grid { + + +static size_t calculate_n(const pl_type& pl) { + ASSERT(!pl.empty() && pl.size() % 2 == 0); + return pl.size() / 2; +} + + +ReducedGaussian::ReducedGaussian(const Spec& spec) : + ReducedGaussian(spec.get_long_vector("pl"), area::BoundingBox(spec), projection::Rotation::make_from_spec(spec)) {} + + +ReducedGaussian::ReducedGaussian(const pl_type& pl, const area::BoundingBox& bbox, projection::Rotation* rotation) : + Reduced(bbox, rotation), + N_(calculate_n(pl)), + pl_(pl), + j_(0), + Nj_(N_ * 2), + x_(Nj_), + y_(range::GaussianLatitude(N_, false).make_range_cropped(bbox.north, bbox.south)) { + ASSERT(Nj_ == pl_.size()); + ASSERT(y_); +} + + +ReducedGaussian::ReducedGaussian(size_t N, const area::BoundingBox& bbox, projection::Rotation* rotation) : + ReducedGaussian(util::reduced_octahedral_pl(N), bbox, rotation) {} + + +Grid::iterator ReducedGaussian::cbegin() const { + return iterator{new geo::iterator::Reduced(*this, 0)}; +} + + +Grid::iterator ReducedGaussian::cend() const { + return iterator{new geo::iterator::Reduced(*this, size())}; +} + + +size_t ReducedGaussian::size() const { + return niacc().back(); +} + + +size_t ReducedGaussian::ni(size_t j) const { + if (!x_.at(j_ + j)) { + auto bbox = boundingBox(); + auto Ni = pl_.at(j_ + j); + ASSERT(Ni >= 0); + + range::RegularLongitude x(static_cast(Ni), 0., 360.); + const_cast>&>(x_)[j].reset(x.make_range_cropped(bbox.west, bbox.east)); + ASSERT(x_[j]); + } + + return x_[j]->size(); +} + + +size_t ReducedGaussian::nj() const { + return y_->size(); +} + + +const std::vector& ReducedGaussian::latitudes() const { + return y_->values(); +} + + +std::vector ReducedGaussian::longitudes(size_t j) const { + if (!x_.at(j_ + j)) { + auto bbox = boundingBox(); + auto Ni = pl_.at(j_ + j); + ASSERT(Ni >= 0); + + range::RegularLongitude x(static_cast(Ni), 0., 360.); + const_cast>&>(x_)[j].reset(x.make_range_cropped(bbox.west, bbox.east)); + ASSERT(x_[j]); + } + + return x_[j]->values(); +} + + +void ReducedGaussian::fill_spec(spec::Custom& custom) const { + Reduced::fill_spec(custom); + + if (pl_ == util::reduced_octahedral_pl(N_)) { + custom.set("grid", "O" + std::to_string(N_)); + } + else { + custom.set("grid", "N" + std::to_string(N_)); + if (!util::reduced_classical_pl_known(N_)) { + custom.set("pl", pl_); + } + } +} + + +Grid* ReducedGaussian::make_grid_cropped(const Area& crop) const { + if (auto cropped(boundingBox()); crop.intersects(cropped)) { + return new ReducedGaussian(pl_, cropped); + } + + throw UserError("ReducedGaussian: cannot crop grid (empty intersection)", Here()); +} + + +struct ReducedGaussianClassical { + static Spec* spec(const std::string& name) { + ASSERT(name.size() > 1 && (name[0] == 'n' || name[0] == 'N')); + + auto N = Translator{}(name.substr(1)); + return new spec::Custom({{"type", "reduced_gg"}, {"N", N}, {"pl", util::reduced_classical_pl(N)}}); + } +}; + + +struct ReducedGaussianOctahedral { + static Spec* spec(const std::string& name) { + ASSERT(name.size() > 1 && (name[0] == 'o' || name[0] == 'O')); + + auto N = Translator{}(name.substr(1)); + return new spec::Custom({{"type", "reduced_gg"}, {"N", N}, {"pl", util::reduced_octahedral_pl(N)}}); + } +}; + + +static const GridRegisterName GRIDNAME1("[nN][1-9][0-9]*"); +static const GridRegisterName GRIDNAME2("[oO][1-9][0-9]*"); + +static const GridRegisterType GRIDTYPE1("reduced_gg"); +static const GridRegisterType GRIDTYPE2("reduced_rotated_gg"); + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/ReducedGaussian.h b/src/eckit/geo/grid/ReducedGaussian.h new file mode 100644 index 000000000..6b74c807e --- /dev/null +++ b/src/eckit/geo/grid/ReducedGaussian.h @@ -0,0 +1,69 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + +#include "eckit/geo/Range.h" +#include "eckit/geo/grid/Reduced.h" +#include "eckit/geo/util.h" + + +namespace eckit::geo::grid { + + +class ReducedGaussian : public Reduced { +public: + // -- Constructors + + explicit ReducedGaussian(const Spec&); + explicit ReducedGaussian(const pl_type&, const area::BoundingBox& = {}, projection::Rotation* = nullptr); + explicit ReducedGaussian(size_t N, const area::BoundingBox& = {}, projection::Rotation* = nullptr); + + // -- Methods + + size_t N() const { return N_; } + + // -- Overridden methods + + iterator cbegin() const override; + iterator cend() const override; + + size_t size() const override; + size_t ni(size_t j) const override; + size_t nj() const override; + +private: + // -- Members + + const size_t N_; + const pl_type pl_; + size_t j_; + size_t Nj_; + + std::vector> x_; + std::unique_ptr y_; + + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; + + const std::vector& latitudes() const override; + std::vector longitudes(size_t i) const override; + + [[nodiscard]] Grid* make_grid_cropped(const Area&) const override; +}; + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/ReducedLL.cc b/src/eckit/geo/grid/ReducedLL.cc new file mode 100644 index 000000000..02c277cf5 --- /dev/null +++ b/src/eckit/geo/grid/ReducedLL.cc @@ -0,0 +1,77 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/grid/ReducedLL.h" + +#include "eckit/geo/iterator/Reduced.h" +#include "eckit/geo/range/RegularLatitude.h" +#include "eckit/geo/range/RegularLongitude.h" +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo::grid { + + +ReducedLL::ReducedLL(const Spec& spec) : ReducedLL(spec.get_long_vector("pl"), area::BoundingBox(spec)) {} + + +ReducedLL::ReducedLL(const pl_type& pl, const area::BoundingBox& bbox) : + Reduced(bbox), pl_(pl), y_(new range::RegularLatitude(pl_.size(), bbox.north, bbox.south)) { + ASSERT(y_); +} + + +Grid::iterator ReducedLL::cbegin() const { + return iterator{new geo::iterator::Reduced(*this, 0)}; +} + + +Grid::iterator ReducedLL::cend() const { + return iterator{new geo::iterator::Reduced(*this, size())}; +} + + +size_t ReducedLL::ni(size_t j) const { + return pl_.at(j); +} + + +size_t ReducedLL::nj() const { + return pl_.size(); +} + + +const std::vector& ReducedLL::latitudes() const { + return y_->values(); +} + + +std::vector ReducedLL::longitudes(size_t j) const { + auto Ni = ni(j); + if (!x_ || x_->size() != Ni) { + auto bbox = boundingBox(); + const_cast&>(x_) = std::make_unique(Ni, bbox.west, bbox.east); + } + + return x_->values(); +} + + +void ReducedLL::fill_spec(spec::Custom& custom) const { + Reduced::fill_spec(custom); + + custom.set("type", "reduced_ll"); + custom.set("pl", pl_); +} + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/ReducedLL.h b/src/eckit/geo/grid/ReducedLL.h new file mode 100644 index 000000000..ac520edab --- /dev/null +++ b/src/eckit/geo/grid/ReducedLL.h @@ -0,0 +1,57 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + +#include "eckit/geo/Range.h" +#include "eckit/geo/grid/Reduced.h" +#include "eckit/geo/util.h" + + +namespace eckit::geo::grid { + + +class ReducedLL : public Reduced { +public: + // -- Constructors + + explicit ReducedLL(const Spec&); + explicit ReducedLL(const pl_type&, const area::BoundingBox& = {}); + + // -- Overridden methods + + iterator cbegin() const override; + iterator cend() const override; + + size_t ni(size_t j) const override; + size_t nj() const override; + +private: + // -- Members + + const pl_type pl_; + + std::unique_ptr x_; + std::unique_ptr y_; + + // -- Overridden methods + + const std::vector& latitudes() const override; + std::vector longitudes(size_t j) const override; + + void fill_spec(spec::Custom&) const override; +}; + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/Regular.cc b/src/eckit/geo/grid/Regular.cc new file mode 100644 index 000000000..60c3f559a --- /dev/null +++ b/src/eckit/geo/grid/Regular.cc @@ -0,0 +1,90 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/grid/Regular.h" + +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/iterator/Regular.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/types/Fraction.h" + + +namespace eckit::geo::grid { + + +namespace { + + +area::BoundingBox make_bounding_box(const Range& lon, const Range& lat) { + auto n = std::max(lat.a(), lat.b()); + auto w = std::min(lon.a(), lon.b()); + auto s = std::min(lat.a(), lat.b()); + auto e = std::max(lon.a(), lon.b()); + return {n, w, s, e}; +} + + +} // namespace + + +double Regular::dx() const { + return x().increment(); +} + + +double Regular::dy() const { + return y().increment(); +} + + +Grid::iterator Regular::cbegin() const { + return iterator{new geo::iterator::Regular(*this, 0)}; +} + + +Grid::iterator Regular::cend() const { + return iterator{new geo::iterator::Regular(*this, size())}; +} + + +const Range& Regular::x() const { + ASSERT(x_ && x_->size() > 0); + return *x_; +} + + +const Range& Regular::y() const { + ASSERT(y_ && y_->size() > 0); + return *y_; +} + + +Regular::Regular(Ranges xy, Projection* projection) : + Grid(make_bounding_box(*xy.first, *xy.second), projection), x_(xy.first), y_(xy.second) { + ASSERT(x_ && x_->size() > 0); + ASSERT(y_ && y_->size() > 0); +} + + +void Regular::fill_spec(spec::Custom& custom) const { + Grid::fill_spec(custom); +} + + +Regular::Ranges::Ranges(Range* x, Range* y) : pair{x, y} { + ASSERT(first != nullptr && second != nullptr); +} + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/Regular.h b/src/eckit/geo/grid/Regular.h new file mode 100644 index 000000000..ff0adc5d2 --- /dev/null +++ b/src/eckit/geo/grid/Regular.h @@ -0,0 +1,84 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + +#include "eckit/geo/Grid.h" +#include "eckit/geo/Range.h" + + +namespace eckit::geo { +class Increments; +namespace iterator { +class Regular; +} +} // namespace eckit::geo + + +namespace eckit::geo::grid { + + +class Regular : public Grid { +public: + // -- Constructors + + explicit Regular(const Spec& spec) : Grid(spec) {} + + // -- Methods + + virtual double dx() const; + virtual double dy() const; + + virtual size_t nx() const { return x_->size(); } + virtual size_t ny() const { return y_->size(); } + + const Range& x() const; + const Range& y() const; + + // -- Overridden methods + + iterator cbegin() const override; + iterator cend() const override; + + size_t size() const final { return nx() * ny(); } + +protected: + // -- Types + + struct Ranges : std::pair { + Ranges(Range*, Range*); + }; + + // -- Constructors + + explicit Regular(Ranges, Projection* = nullptr); + + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; + +private: + // -- Members + + std::unique_ptr x_; + std::unique_ptr y_; + + // -- Friends + + friend class geo::iterator::Regular; +}; + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/RegularGaussian.cc b/src/eckit/geo/grid/RegularGaussian.cc new file mode 100644 index 000000000..0f4090399 --- /dev/null +++ b/src/eckit/geo/grid/RegularGaussian.cc @@ -0,0 +1,71 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/grid/RegularGaussian.h" + +#include + +#include "eckit/geo/range/GaussianLatitude.h" +#include "eckit/geo/range/RegularLongitude.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/utils/Translator.h" + + +namespace eckit::geo::grid { + + +RegularGaussian::RegularGaussian(const Spec& spec) : + RegularGaussian(spec.get_unsigned("N"), + *std::unique_ptr(area::BoundingBox::make_from_spec(spec)), + projection::Rotation::make_from_spec(spec)) {} + + +RegularGaussian::RegularGaussian(size_t N, const area::BoundingBox& bbox, projection::Rotation* rotation) : + Regular({range::RegularLongitude(4 * N, 0., 360.).make_range_cropped(bbox.west, bbox.east), + range::GaussianLatitude(N, false).make_range_cropped(bbox.north, bbox.south)}, + rotation), + N_(N) { + ASSERT(size() > 0); +} + + +Grid* RegularGaussian::make_grid_cropped(const Area& crop) const { + if (auto cropped(boundingBox()); crop.intersects(cropped)) { + return new RegularGaussian(N_, cropped); + } + + throw UserError("RegularGaussian: cannot crop grid (empty intersection)", Here()); +} + + +Spec* RegularGaussian::spec(const std::string& name) { + ASSERT(name.size() > 1 && (name[0] == 'f' || name[0] == 'F')); + + auto N = Translator{}(name.substr(1)); + return new spec::Custom({{"type", "regular_gg"}, {"N", N}}); +} + + +void RegularGaussian::fill_spec(spec::Custom& custom) const { + Regular::fill_spec(custom); + + custom.set("grid", "F" + std::to_string(N_)); +} + + +static const GridRegisterName GRIDNAME("[fF][1-9][0-9]*"); + +static const GridRegisterType GRIDTYPE1("regular_gg"); +static const GridRegisterType GRIDTYPE2("rotated_gg"); + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/RegularGaussian.h b/src/eckit/geo/grid/RegularGaussian.h new file mode 100644 index 000000000..372bdde1e --- /dev/null +++ b/src/eckit/geo/grid/RegularGaussian.h @@ -0,0 +1,47 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/grid/Regular.h" + + +namespace eckit::geo::grid { + + +class RegularGaussian final : public Regular { +public: + // -- Constructors + + explicit RegularGaussian(const Spec&); + explicit RegularGaussian(size_t N, const area::BoundingBox& = {}, projection::Rotation* = nullptr); + + // -- Methods + + size_t N() const { return N_; } + + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; + + [[nodiscard]] Grid* make_grid_cropped(const Area&) const override; + + // -- Class members + + [[nodiscard]] static Spec* spec(const std::string& name); + +private: + const size_t N_; +}; + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/RegularLL.cc b/src/eckit/geo/grid/RegularLL.cc new file mode 100644 index 000000000..aea5cfaae --- /dev/null +++ b/src/eckit/geo/grid/RegularLL.cc @@ -0,0 +1,116 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/grid/RegularLL.h" + +#include +#include + +#include "eckit/geo/Increments.h" +#include "eckit/geo/range/RegularLatitude.h" +#include "eckit/geo/range/RegularLongitude.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/utils/Translator.h" + + +namespace eckit::geo::grid { + + +#define POSITIVE_REAL "[+]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][-+][0-9]+)?" +static const std::string REGULAR_LL_PATTERN("(" POSITIVE_REAL ")/(" POSITIVE_REAL ")"); +#undef POSITIVE_REAL + + +RegularLL::RegularLL(const Spec& spec) : + RegularLL(Increments{spec}, area::BoundingBox{spec}, projection::Rotation::make_from_spec(spec), + [&spec]() -> PointLonLat { + std::vector v(2); + if (spec.get("reference_lon", v[0]) && spec.get("reference_lat", v[1])) { + return {v[0], v[1]}; + } + + if (spec.get("reference_lonlat", v) && v.size() == 2) { + return {v[0], v[1]}; + } + + area::BoundingBox area{spec}; + return {area.west, area.south}; + }()) { + ASSERT(size() > 0); +} + + +RegularLL::RegularLL(const Increments& inc, const area::BoundingBox& bbox, projection::Rotation* rotation) : + RegularLL(inc, bbox, rotation, {bbox.south, bbox.west}) {} + + +RegularLL::RegularLL(const Increments& inc, const area::BoundingBox& bbox, projection::Rotation* rotation, + const PointLonLat& ref) : + Regular({new range::RegularLongitude(inc.dx, bbox.west, bbox.east, ref.lon, 0.), + new range::RegularLatitude(inc.dy, bbox.north, bbox.south, ref.lat, 0.)}, + rotation) { + ASSERT(size() > 0); +} + + +Spec* RegularLL::spec(const std::string& name) { + std::smatch match; + std::regex_match(name, match, std::regex(REGULAR_LL_PATTERN)); + ASSERT(match.size() == 9); + + auto d = Translator{}; + + return new spec::Custom({{"type", "regular_ll"}, {"grid", std::vector{d(match[1]), d(match[5])}}}); +} + + +void RegularLL::fill_spec(spec::Custom& custom) const { + Regular::fill_spec(custom); + + custom.set("grid", std::vector{dx(), dy()}); + + if (!boundingBox().global()) { + custom.set("shape", std::vector{static_cast(nx()), static_cast(ny())}); + } + + boundingBox().fill_spec(custom); +} + + +Grid* RegularLL::make_grid_cropped(const Area& crop) const { + if (auto cropped(boundingBox()); crop.intersects(cropped)) { + return new RegularLL({dx(), dy()}, cropped); + } + + throw UserError("RegularLL: cannot crop grid (empty intersection)", Here()); +} + + +area::BoundingBox* RegularLL::calculate_bbox() const { + // FIXME depends on ordering + auto n = std::max(y().a(), y().b()); + auto s = std::min(y().a(), y().b()); + + auto w = x().a(); + auto e = x().periodic() ? w + PointLonLat::FULL_ANGLE : x().b(); + + return new area::BoundingBox{n, w, s, e}; +} + + +static const GridRegisterName GRIDNAME(REGULAR_LL_PATTERN); + +static const GridRegisterType GRIDTYPE1("regular_ll"); +static const GridRegisterType GRIDTYPE2("rotated_ll"); + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/RegularLL.h b/src/eckit/geo/grid/RegularLL.h new file mode 100644 index 000000000..c54c75f27 --- /dev/null +++ b/src/eckit/geo/grid/RegularLL.h @@ -0,0 +1,42 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/grid/Regular.h" + + +namespace eckit::geo::grid { + + +class RegularLL final : public Regular { +public: + // -- Constructors + + explicit RegularLL(const Spec&); + explicit RegularLL(const Increments&, const area::BoundingBox& = {}, projection::Rotation* = nullptr); + RegularLL(const Increments&, const area::BoundingBox&, projection::Rotation*, const PointLonLat& ref); + + // -- Methods + + [[nodiscard]] static Spec* spec(const std::string& name); + + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; + + [[nodiscard]] Grid* make_grid_cropped(const Area&) const override; + [[nodiscard]] area::BoundingBox* calculate_bbox() const override; +}; + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/RegularXY.cc b/src/eckit/geo/grid/RegularXY.cc new file mode 100644 index 000000000..aa3da4e1b --- /dev/null +++ b/src/eckit/geo/grid/RegularXY.cc @@ -0,0 +1,62 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/grid/RegularXY.h" + +#include + +#include "eckit/geo/Increments.h" +#include "eckit/geo/Projection.h" +#include "eckit/geo/Shape.h" +#include "eckit/geo/range/RegularCartesian.h" +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo::grid { + + +Regular::Ranges RegularXY::make_ranges_from_spec(const Spec& spec) { +#if 0 + Increments inc(spec); + Shape shape(spec); + + std::unique_ptr projection(ProjectionFactory::instance().get("proj").create(spec)); + + std::vector v(2); + auto first_lonlat((spec.get("first_lon", v[0]) && spec.get("first_lat", v[1])) + || (spec.get("first_lonlat", v) && v.size() == 2) + ? PointLonLat{v[0], v[1]} + : throw SpecNotFound("['first_lonlat' = ['first_lon', 'first_lat'] expected", Here())); + + // TODO; + + + Point2 a = std::get(projection->inv(first_lonlat)); + Point2 b{a.X + inc.dx * static_cast(shape.nx - 1), a.Y - inc.dy * static_cast(shape.ny - 1)}; + + return {new range::RegularCartesian(shape.nx, a.X, b.X), new range::RegularCartesian(shape.ny, a.Y, b.Y)}; +#else + return {new range::RegularCartesian(11, 0, 10), new range::RegularCartesian(11, 0, 10)}; +#endif +} + + +void RegularXY::fill_spec(spec::Custom& custom) const { + Regular::fill_spec(custom); + + custom.set("grid", std::vector{dx(), dy()}); + custom.set("shape", std::vector{static_cast(nx()), static_cast(ny())}); + custom.set("first_lonlat", std::vector{first_lonlat.lon, first_lonlat.lat}); +} + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/RegularXY.h b/src/eckit/geo/grid/RegularXY.h new file mode 100644 index 000000000..a6c677cca --- /dev/null +++ b/src/eckit/geo/grid/RegularXY.h @@ -0,0 +1,52 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/grid/Regular.h" + + +namespace eckit::geo::grid { + + +class RegularXY : public Regular { +public: + // -- Constructors + + using Regular::Regular; + + // -- Methods + + double dlon() const { return dx(); } + double dlat() const { return dy(); } + + size_t nlon() const { return x().size(); } + size_t nlat() const { return y().size(); } + +protected: + // -- Methods + + [[nodiscard]] static Ranges make_ranges_from_spec(const Spec&); + + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; + +private: + // -- Members + + PointLonLat first_lonlat; + Point2 first_xy; +}; + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/Unstructured.cc b/src/eckit/geo/grid/Unstructured.cc new file mode 100644 index 000000000..ced92f6f0 --- /dev/null +++ b/src/eckit/geo/grid/Unstructured.cc @@ -0,0 +1,49 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/grid/Unstructured.h" + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/iterator/Unstructured.h" +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo::grid { + + +Unstructured::Unstructured(std::vector&& points) : Grid(area::BoundingBox{}), points_(points) {} + + +Grid::iterator Unstructured::cbegin() const { + return iterator{new geo::iterator::Unstructured(*this, 0, points_)}; +} + + +Grid::iterator Unstructured::cend() const { + return iterator{new geo::iterator::Unstructured(*this)}; +} + + +Spec* Unstructured::spec(const std::string& name) { + return SpecByUID::instance().get(name).spec(); +} + + +void Unstructured::fill_spec(spec::Custom& custom) const { + Grid::fill_spec(custom); + + custom.set("type", "unstructured"); + custom.set("uid", uid()); +} + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/Unstructured.h b/src/eckit/geo/grid/Unstructured.h new file mode 100644 index 000000000..2424bd9f8 --- /dev/null +++ b/src/eckit/geo/grid/Unstructured.h @@ -0,0 +1,66 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + +#include "eckit/geo/Grid.h" + + +namespace eckit::geo::iterator { +class Unstructured; +} + + +namespace eckit::geo::grid { + + +class Unstructured final : public Grid { +public: + // -- Constructors + + explicit Unstructured(std::vector&&); + + // -- Overridden methods + + iterator cbegin() const override; + iterator cend() const override; + + size_t size() const override { return points_.size(); } + + bool includesNorthPole() const override { return true; } + bool includesSouthPole() const override { return true; } + bool isPeriodicWestEast() const override { return true; } + + std::vector to_points() const override { return points_; } + + // -- Class methods + + [[nodiscard]] static Spec* spec(const std::string& name); + +private: + // -- Members + + const std::vector points_; + + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; + + // -- Friends + + friend class geo::iterator::Unstructured; +}; + + +} // namespace eckit::geo::grid diff --git a/src/eckit/geo/grid/regular-xy/LambertAzimuthalEqualArea.cc b/src/eckit/geo/grid/regular-xy/LambertAzimuthalEqualArea.cc new file mode 100644 index 000000000..be5ee261a --- /dev/null +++ b/src/eckit/geo/grid/regular-xy/LambertAzimuthalEqualArea.cc @@ -0,0 +1,34 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/grid/regular-xy/LambertAzimuthalEqualArea.h" + +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo::grid::regularxy { + + +static const GridRegisterType GRIDTYPE("lambert_azimuthal_equal_area"); + + +void LambertAzimuthalEqualArea::fill_spec(spec::Custom& custom) const { + RegularXY::fill_spec(custom); + + custom.set("type", "lambert_azimuthal_equal_area"); + + // FIXME a lot more stuff to add here! + //... +} + + +} // namespace eckit::geo::grid::regularxy diff --git a/src/eckit/geo/grid/regular-xy/LambertAzimuthalEqualArea.h b/src/eckit/geo/grid/regular-xy/LambertAzimuthalEqualArea.h new file mode 100644 index 000000000..987b453a1 --- /dev/null +++ b/src/eckit/geo/grid/regular-xy/LambertAzimuthalEqualArea.h @@ -0,0 +1,33 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/grid/RegularXY.h" + + +namespace eckit::geo::grid::regularxy { + + +class LambertAzimuthalEqualArea final : public RegularXY { +public: + // -- Constructors + + explicit LambertAzimuthalEqualArea(const Spec& spec) : RegularXY(RegularXY::make_ranges_from_spec(spec)) {} + + // -- Overridden methods + + void fill_spec(spec::Custom& custom) const override; +}; + + +} // namespace eckit::geo::grid::regularxy diff --git a/src/eckit/geo/grid/regular-xy/LambertConformalConic.cc b/src/eckit/geo/grid/regular-xy/LambertConformalConic.cc new file mode 100644 index 000000000..a4700daeb --- /dev/null +++ b/src/eckit/geo/grid/regular-xy/LambertConformalConic.cc @@ -0,0 +1,34 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/grid/regular-xy/LambertConformalConic.h" + +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo::grid::regularxy { + + +static const GridRegisterType GRIDTYPE("lambert"); + + +void LambertConformalConic::fill_spec(spec::Custom& custom) const { + RegularXY::fill_spec(custom); + + custom.set("type", "lambert"); + + // FIXME a lot more stuff to add here! + //... +} + + +} // namespace eckit::geo::grid::regularxy diff --git a/src/eckit/geo/grid/regular-xy/LambertConformalConic.h b/src/eckit/geo/grid/regular-xy/LambertConformalConic.h new file mode 100644 index 000000000..197b7765c --- /dev/null +++ b/src/eckit/geo/grid/regular-xy/LambertConformalConic.h @@ -0,0 +1,33 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/grid/RegularXY.h" + + +namespace eckit::geo::grid::regularxy { + + +class LambertConformalConic final : public RegularXY { +public: + // -- Constructors + + explicit LambertConformalConic(const Spec& spec) : RegularXY(RegularXY::make_ranges_from_spec(spec)) {} + + // -- Overridden methods + + void fill_spec(spec::Custom& custom) const override; +}; + + +} // namespace eckit::geo::grid::regularxy diff --git a/src/eckit/geo/grid/regular-xy/Mercator.cc b/src/eckit/geo/grid/regular-xy/Mercator.cc new file mode 100644 index 000000000..09e4264b2 --- /dev/null +++ b/src/eckit/geo/grid/regular-xy/Mercator.cc @@ -0,0 +1,34 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/grid/regular-xy/Mercator.h" + +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo::grid::regularxy { + + +static const GridRegisterType GRIDTYPE("mercator"); + + +void Mercator::fill_spec(spec::Custom& custom) const { + RegularXY::fill_spec(custom); + + custom.set("type", "mercator"); + + // FIXME a lot more stuff to add here! + //... +} + + +} // namespace eckit::geo::grid::regularxy diff --git a/src/eckit/geo/grid/regular-xy/Mercator.h b/src/eckit/geo/grid/regular-xy/Mercator.h new file mode 100644 index 000000000..40834ba78 --- /dev/null +++ b/src/eckit/geo/grid/regular-xy/Mercator.h @@ -0,0 +1,33 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/grid/RegularXY.h" + + +namespace eckit::geo::grid::regularxy { + + +class Mercator final : public RegularXY { +public: + // -- Constructors + + explicit Mercator(const Spec& spec) : RegularXY(RegularXY::make_ranges_from_spec(spec)) {} + + // -- Overridden methods + + void fill_spec(spec::Custom& custom) const override; +}; + + +} // namespace eckit::geo::grid::regularxy diff --git a/src/eckit/geo/grid/regular-xy/PolarStereographic.cc b/src/eckit/geo/grid/regular-xy/PolarStereographic.cc new file mode 100644 index 000000000..dc88d6c99 --- /dev/null +++ b/src/eckit/geo/grid/regular-xy/PolarStereographic.cc @@ -0,0 +1,34 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/grid/regular-xy/PolarStereographic.h" + +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo::grid::regularxy { + + +static const GridRegisterType GRIDTYPE("polar_stereographic"); + + +void PolarStereographic::fill_spec(spec::Custom& custom) const { + RegularXY::fill_spec(custom); + + custom.set("type", "polar_stereographic"); + + // FIXME a lot more stuff to add here! + //... +} + + +} // namespace eckit::geo::grid::regularxy diff --git a/src/eckit/geo/grid/regular-xy/PolarStereographic.h b/src/eckit/geo/grid/regular-xy/PolarStereographic.h new file mode 100644 index 000000000..a42530945 --- /dev/null +++ b/src/eckit/geo/grid/regular-xy/PolarStereographic.h @@ -0,0 +1,33 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/grid/RegularXY.h" + + +namespace eckit::geo::grid::regularxy { + + +class PolarStereographic final : public RegularXY { +public: + // -- Constructors + + explicit PolarStereographic(const Spec& spec) : RegularXY(RegularXY::make_ranges_from_spec(spec)) {} + + // -- Overridden methods + + void fill_spec(spec::Custom& custom) const override; +}; + + +} // namespace eckit::geo::grid::regularxy diff --git a/src/eckit/geo/grid/regular-xy/SpaceView.cc b/src/eckit/geo/grid/regular-xy/SpaceView.cc new file mode 100644 index 000000000..1a74628ab --- /dev/null +++ b/src/eckit/geo/grid/regular-xy/SpaceView.cc @@ -0,0 +1,34 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/grid/regular-xy/SpaceView.h" + +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo::grid::regularxy { + + +static const GridRegisterType GRIDTYPE("space_view"); + + +void SpaceView::fill_spec(spec::Custom& custom) const { + RegularXY::fill_spec(custom); + + custom.set("type", "space_view"); + + // FIXME a lot more stuff to add here! + //... +} + + +} // namespace eckit::geo::grid::regularxy diff --git a/src/eckit/geo/grid/regular-xy/SpaceView.h b/src/eckit/geo/grid/regular-xy/SpaceView.h new file mode 100644 index 000000000..85a0a9614 --- /dev/null +++ b/src/eckit/geo/grid/regular-xy/SpaceView.h @@ -0,0 +1,33 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/grid/RegularXY.h" + + +namespace eckit::geo::grid::regularxy { + + +class SpaceView final : public RegularXY { +public: + // -- Constructors + + explicit SpaceView(const Spec& spec) : RegularXY(RegularXY::make_ranges_from_spec(spec)) {} + + // -- Overridden methods + + void fill_spec(spec::Custom& custom) const override; +}; + + +} // namespace eckit::geo::grid::regularxy diff --git a/src/eckit/geo/iterator/Reduced.cc b/src/eckit/geo/iterator/Reduced.cc new file mode 100644 index 000000000..5f1ba889e --- /dev/null +++ b/src/eckit/geo/iterator/Reduced.cc @@ -0,0 +1,100 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/iterator/Reduced.h" + +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/grid/Reduced.h" + + +namespace eckit::geo::iterator { + + +Reduced::Reduced(const Grid& grid, size_t index) : + grid_(dynamic_cast(grid)), + latitudes_(grid_.latitudes()), + niacc_(grid_.niacc()), + index_(index), + size_(grid.size()) { + if (index_ < size_) { + longitudes_j_ = grid_.longitudes(j_ = j(index_)); + ASSERT(niacc_[j_] <= index && index_ < niacc_[j_ + 1]); + ASSERT(latitudes_.size() == grid_.nj()); + } +} + + +bool Reduced::operator==(const Iterator& other) const { + const auto* another = dynamic_cast(&other); + return another != nullptr && index_ == another->index_; +} + + +bool Reduced::operator++() { + if (index_++; index_ < size_) { + if (!(index_ < niacc_[j_ + 1])) { + longitudes_j_ = grid_.longitudes(++j_); + } + + ASSERT(niacc_[j_] <= index_ && index_ < niacc_[j_ + 1]); + return true; + } + + index_ = size_; // ensure it's invalid + return false; +} + + +bool Reduced::operator+=(difference_type d) { + if (auto di = static_cast(index_); 0 <= di + d && di + d < static_cast(size_)) { + if (index_ = static_cast(di + d); !(niacc_[j_] <= index_ && index_ < niacc_[j_ + 1])) { + longitudes_j_ = grid_.longitudes(j_ = j(index_)); + } + + ASSERT(niacc_[j_] <= index_ && index_ < niacc_[j_ + 1]); + return true; + } + + index_ = size_; // ensure it's invalid + return false; +} + + +Reduced::operator bool() const { + return index_ < size_; +} + + +Point Reduced::operator*() const { + return PointLonLat{longitudes_j_.at(index_ - niacc_[j_]), latitudes_.at(j_)}; +} + + +size_t Reduced::j(size_t idx) const { + ASSERT(idx < size_); + + auto dist = std::distance(niacc_.begin(), std::upper_bound(niacc_.begin(), niacc_.end(), idx)); + ASSERT(1 <= dist && dist <= niacc_.size() - 1); + + return static_cast(dist - 1); +} + + +void Reduced::fill_spec(spec::Custom&) const { + // FIXME implement +} + + +} // namespace eckit::geo::iterator diff --git a/src/eckit/geo/iterator/Reduced.h b/src/eckit/geo/iterator/Reduced.h new file mode 100644 index 000000000..05d8d8c5d --- /dev/null +++ b/src/eckit/geo/iterator/Reduced.h @@ -0,0 +1,62 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + +#include "eckit/geo/Iterator.h" + + +namespace eckit::geo::grid { +class Reduced; +} + + +namespace eckit::geo::iterator { + + +class Reduced final : public geo::Iterator { +public: + // -- Constructors + + explicit Reduced(const Grid&, size_t index = 0); + +private: + // -- Members + + const grid::Reduced& grid_; + std::vector longitudes_j_; + const std::vector& latitudes_; + const std::vector& niacc_; + size_t j_; + size_t index_; + const size_t size_; + + // -- Overridden operators + + bool operator==(const Iterator&) const override; + bool operator++() override; + bool operator+=(difference_type) override; + explicit operator bool() const override; + Point operator*() const override; + + // -- Overridden methods + + size_t index() const override { return index_; } + size_t j(size_t idx) const; + + void fill_spec(spec::Custom&) const override; +}; + + +} // namespace eckit::geo::iterator diff --git a/src/eckit/geo/iterator/Regular.cc b/src/eckit/geo/iterator/Regular.cc new file mode 100644 index 000000000..a7d8673e2 --- /dev/null +++ b/src/eckit/geo/iterator/Regular.cc @@ -0,0 +1,75 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/iterator/Regular.h" + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/grid/Regular.h" + + +namespace eckit::geo::iterator { + + +Regular::Regular(const grid::Regular& grid, size_t index) : + grid_(grid), + x_(grid.x().values()), + y_(grid.y().values()), + i_(0), + j_(0), + index_(index), + nx_(x_.size()), + ny_(y_.size()), + size_(nx_ * nx_) {} + + +bool Regular::operator==(const Iterator& other) const { + const auto* another = dynamic_cast(&other); + return another != nullptr && index_ == another->index_; +} + + +bool Regular::operator++() { + if (index_++, i_++; index_ < size_) { + if (i_ >= nx_) { + i_ = 0; + j_++; + } + + return true; + } + + index_ = size_; // ensure it's invalid + return false; +} + + +bool Regular::operator+=(difference_type d) { + NOTIMP; +} + + +Regular::operator bool() const { + return index_ < size_; +} + + +Point Regular::operator*() const { + return PointLonLat{x_.at(i_), y_.at(j_)}; +} + + +void Regular::fill_spec(spec::Custom&) const { + // FIXME implement +} + + +} // namespace eckit::geo::iterator diff --git a/src/eckit/geo/iterator/Regular.h b/src/eckit/geo/iterator/Regular.h new file mode 100644 index 000000000..5bf27c948 --- /dev/null +++ b/src/eckit/geo/iterator/Regular.h @@ -0,0 +1,64 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + +#include "eckit/geo/Iterator.h" + + +namespace eckit::geo { +class Range; +namespace grid { +class Regular; +} +} // namespace eckit::geo + + +namespace eckit::geo::iterator { + + +class Regular final : public Iterator { +public: + // -- Constructors + + explicit Regular(const grid::Regular&, size_t index = 0); + +private: + // -- Members + + const grid::Regular& grid_; + const std::vector& x_; + const std::vector& y_; + size_t i_; + size_t j_; + size_t index_; + const size_t nx_; + const size_t ny_; + const size_t size_; + + // -- Overridden methods + + bool operator==(const Iterator&) const override; + bool operator++() override; + bool operator+=(difference_type) override; + explicit operator bool() const override; + Point operator*() const override; + + size_t index() const override { return index_; } + + void fill_spec(spec::Custom&) const override; +}; + + +} // namespace eckit::geo::iterator diff --git a/src/eckit/geo/iterator/Unstructured.cc b/src/eckit/geo/iterator/Unstructured.cc new file mode 100644 index 000000000..b647beeba --- /dev/null +++ b/src/eckit/geo/iterator/Unstructured.cc @@ -0,0 +1,130 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/iterator/Unstructured.h" + +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/Grid.h" +#include "eckit/geo/grid/Unstructured.h" + + +namespace eckit::geo::iterator { + + +namespace { + + +struct LonLatReference : Unstructured::Container { + explicit LonLatReference(const std::vector& longitudes, const std::vector& latitudes) : + longitudes(longitudes), latitudes(latitudes) { + ASSERT(longitudes.size() == latitudes.size()); + } + + Point get(size_t index) const override { return PointLonLat{longitudes.at(index), latitudes.at(index)}; } + size_t size() const override { return longitudes.size(); } + + const std::vector& longitudes; + const std::vector& latitudes; +}; + + +struct PointsReference : Unstructured::Container { + explicit PointsReference(const std::vector& points) : points(points) {} + + Point get(size_t index) const override { return points.at(index); } + size_t size() const override { return points.size(); } + + const std::vector& points; +}; + + +struct PointsMove : Unstructured::Container { + explicit PointsMove(std::vector&& points) : points(points) {} + + Point get(size_t index) const override { return points.at(index); } + size_t size() const override { return points.size(); } + + const std::vector points; +}; + + +} // namespace + + +Unstructured::Unstructured(const Grid& grid, size_t index, const std::vector& longitudes, + const std::vector& latitudes) : + container_(new LonLatReference(longitudes, latitudes)), index_(index), size_(container_->size()), uid_(grid.uid()) { + ASSERT(container_->size() == grid.size()); +} + + +Unstructured::Unstructured(const Grid& grid, size_t index, const std::vector& points) : + container_(new PointsReference(points)), index_(index), size_(container_->size()), uid_(grid.uid()) { + ASSERT(container_->size() == grid.size()); +} + + +Unstructured::Unstructured(const Grid& grid, size_t index, std::vector&& points) : + container_(new PointsMove(std::move(points))), index_(index), size_(container_->size()), uid_(grid.uid()) { + ASSERT(container_->size() == grid.size()); +} + + +Unstructured::Unstructured(const Grid& grid) : index_(grid.size()), size_(grid.size()), uid_(grid.uid()) {} + + +bool Unstructured::operator==(const geo::Iterator& other) const { + const auto* another = dynamic_cast(&other); + return another != nullptr && index_ == another->index_ && uid_ == another->uid_; +} + + +bool Unstructured::operator++() { + if (index_++; index_ < size_) { + return true; + } + + index_ = size_; // ensure it's invalid + return false; +} + + +bool Unstructured::operator+=(difference_type d) { + if (auto di = static_cast(index_); 0 <= di + d && di + d < static_cast(size_)) { + index_ = static_cast(di + d); + return true; + } + + index_ = size_; // ensure it's invalid + return false; +} + + +Unstructured::operator bool() const { + return index_ < size_; +} + + +Point Unstructured::operator*() const { + ASSERT(container_); + return container_->get(index_); +} + + +void Unstructured::fill_spec(spec::Custom&) const { + // FIXME implement +} + + +} // namespace eckit::geo::iterator diff --git a/src/eckit/geo/iterator/Unstructured.h b/src/eckit/geo/iterator/Unstructured.h new file mode 100644 index 000000000..cc163221c --- /dev/null +++ b/src/eckit/geo/iterator/Unstructured.h @@ -0,0 +1,74 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + +#include "eckit/geo/Iterator.h" + + +namespace eckit::geo::iterator { + + +class Unstructured final : public Iterator { +public: + // -- Types + + struct Container { + Container() = default; + virtual ~Container() = default; + + Container(const Container&) = delete; + Container(Container&&) = delete; + + Container& operator=(const Container&) = delete; + Container& operator=(Container&&) = delete; + + virtual Point get(size_t index) const = 0; + virtual size_t size() const = 0; + }; + + // -- Constructors + + explicit Unstructured(const Grid&, size_t index, const std::vector& longitudes, + const std::vector& latitudes); + explicit Unstructured(const Grid&, size_t index, const std::vector&); + explicit Unstructured(const Grid&, size_t index, std::vector&&); + + explicit Unstructured(const Grid&); + +private: + // -- Members + + std::unique_ptr container_; + size_t index_; + const size_t size_; + const std::string uid_; + + // -- Overridden methods + + bool operator==(const geo::Iterator&) const override; + bool operator++() override; + bool operator+=(difference_type) override; + + explicit operator bool() const override; + Point operator*() const override; + + size_t index() const override { return index_; } + + void fill_spec(spec::Custom&) const override; +}; + + +} // namespace eckit::geo::iterator diff --git a/src/eckit/geo/polygon/LonLatPolygon.cc b/src/eckit/geo/polygon/LonLatPolygon.cc new file mode 100644 index 000000000..53aae168f --- /dev/null +++ b/src/eckit/geo/polygon/LonLatPolygon.cc @@ -0,0 +1,182 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/polygon/LonLatPolygon.h" + +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/PointLonLat.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::polygon { + + +namespace { + +inline bool is_approximately_equal(double a, double b) { + return types::is_approximately_equal(a, b, 1e-10); +} + +inline bool is_approximately_greater_or_equal(double a, double b) { + return a >= b || is_approximately_equal(a, b); +} + +inline double cross_product_analog(const Point2& A, const Point2& B, const Point2& C) { + return (A.X - C.X) * (B.Y - C.Y) - (A.Y - C.Y) * (B.X - C.X); +} + +inline int on_direction(double a, double b, double c) { + return a <= b && b <= c ? 1 : c <= b && b <= a ? -1 : 0; +}; + +inline int on_side(const Point2& P, const Point2& A, const Point2& B) { + const auto p = cross_product_analog(P, A, B); + return is_approximately_equal(p, 0) ? 0 : p > 0 ? 1 : -1; +} + +constexpr int LON = 0; +constexpr int LAT = 1; + +inline Point2 componentsMin(const Point2& A, const Point2& B) { + return {std::min(A.X, B.X), std::min(A.Y, B.Y)}; +} + +inline Point2 componentsMax(const Point2& A, const Point2& B) { + return {std::max(A.X, B.X), std::max(A.Y, B.Y)}; +} + +} // namespace + + +LonLatPolygon::LonLatPolygon(const std::vector& points, bool includePoles) : container_type(points) { + ASSERT(points.size() > 1); + ASSERT(is_approximately_equal(points.front()[LON], points.back()[LON]) + && is_approximately_equal(points.front()[LAT], points.back()[LAT])); + + if (points.size() > 2) { + clear(); // assumes reserved size is kept + push_back(points.front()); + push_back(points[1]); + + for (size_t i = 2; i < points.size(); ++i) { + auto A = points[i]; + + // if new point is aligned with existing edge (cross product ~= 0) make the edge longer + const auto& B = back(); + const auto& C = operator[](size() - 2); + if (is_approximately_equal(0., cross_product_analog(A, B, C))) { + back() = A; + continue; + } + + push_back(A); + } + } + + max_ = min_ = front(); + for (const auto& p : *this) { + min_ = componentsMin(min_, p); + max_ = componentsMax(max_, p); + } + + includeNorthPole_ = includePoles && is_approximately_equal(max_[LAT], 90.); + includeSouthPole_ = includePoles && is_approximately_equal(min_[LAT], -90.); + ASSERT(is_approximately_greater_or_equal(min_[LAT], -90.)); + ASSERT(is_approximately_greater_or_equal(90., max_[LAT])); + + quickCheckLongitude_ = is_approximately_greater_or_equal(360, max_[LON] - min_[LON]); +} + +void LonLatPolygon::print(std::ostream& out) const { + out << "["; + const auto* sep = ""; + for (const auto& p : *this) { + out << sep << p; + sep = ", "; + } + out << "]"; +} + +std::ostream& operator<<(std::ostream& out, const LonLatPolygon& pc) { + pc.print(out); + return out; +} + +bool LonLatPolygon::contains(const Point2& Plonlat, bool normalise_angle) const { + if (!normalise_angle) { + PointLonLat::assert_latitude_range({Plonlat[LON], Plonlat[LAT]}); + } + + Point2 Q{PointLonLat::normalise_angle_to_minimum(Plonlat[LON], min_[LON]), Plonlat[LAT]}; + + // check poles + if (includeNorthPole_ && is_approximately_equal(Q[LAT], 90.)) { + return true; + } + if (includeSouthPole_ && is_approximately_equal(Q[LAT], -90.)) { + return true; + } + + // check bounding box + if (!is_approximately_greater_or_equal(Q[LAT], min_[LAT]) + || !is_approximately_greater_or_equal(max_[LAT], Q[LAT])) { + return false; + } + if (quickCheckLongitude_) { + if (!is_approximately_greater_or_equal(Q[LON], min_[LON]) + || !is_approximately_greater_or_equal(max_[LON], Q[LON])) { + return false; + } + } + + do { + // winding number + int wn = 0; + int prev = 0; + + // loop on polygon edges + for (size_t i = 1; i < size(); ++i) { + const auto& A = operator[](i - 1); + const auto& B = operator[](i); + + // check point-edge side and direction, testing if P is on|above|below (in latitude) of a A,B polygon edge, + // by: + // - intersecting "up" on forward crossing & P above edge, or + // - intersecting "down" on backward crossing & P below edge + const auto direction = on_direction(A[LAT], Q[LAT], B[LAT]); + if (direction != 0) { + const auto side = on_side(Q, A, B); + if (side == 0 && on_direction(A[LON], Q[LON], B[LON]) != 0) { + return true; + } + if ((prev != 1 && direction > 0 && side > 0) || (prev != -1 && direction < 0 && side < 0)) { + prev = direction; + wn += direction; + } + } + } + + // wn == 0 only when P is outside + if (wn != 0) { + return true; + } + + Q[LON] += 360.; + } while (Q[LON] <= max_[LON]); + + return false; +} + + +} // namespace eckit::geo::polygon diff --git a/src/eckit/geo/polygon/LonLatPolygon.h b/src/eckit/geo/polygon/LonLatPolygon.h new file mode 100644 index 000000000..5dd8cb5c4 --- /dev/null +++ b/src/eckit/geo/polygon/LonLatPolygon.h @@ -0,0 +1,81 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + +#include "eckit/geo/Point2.h" + + +namespace eckit::geo::polygon { + + +class LonLatPolygon : protected std::vector { +public: + // -- Types + + using container_type = std::vector; + using container_type::value_type; + + // -- Constructors + + explicit LonLatPolygon(const container_type& points, bool includePoles = true); + + template + LonLatPolygon(Point2Iterator begin, Point2Iterator end, bool includePoles = true) : + LonLatPolygon(container_type(begin, end), includePoles) {} + + LonLatPolygon(const LonLatPolygon&) = default; + LonLatPolygon(LonLatPolygon&&) = default; + + // -- Destructor + + virtual ~LonLatPolygon() = default; + + // -- Operators + + LonLatPolygon& operator=(const LonLatPolygon&) = default; + LonLatPolygon& operator=(LonLatPolygon&&) = default; + + // -- Methods + + const Point2& max() const { return max_; } + const Point2& min() const { return min_; } + + using container_type::operator[]; + using container_type::size; + + /// @brief Point-in-polygon test based on winding number + /// @note reference Inclusion of a Point in a Polygon + /// @param[in] P given point + /// @param[in] normalise_angle normalise point angles + /// @return if point (lon,lat) is in polygon + bool contains(const Point2& Plonlat, bool normalise_angle = false) const; + +private: + // -- Methods + + void print(std::ostream&) const; + friend std::ostream& operator<<(std::ostream&, const LonLatPolygon&); + + // -- Members + + Point2 max_; + Point2 min_; + bool includeNorthPole_; + bool includeSouthPole_; + bool quickCheckLongitude_; +}; + + +} // namespace eckit::geo::polygon diff --git a/src/eckit/geo/polygon/Polygon.cc b/src/eckit/geo/polygon/Polygon.cc new file mode 100644 index 000000000..9808f9406 --- /dev/null +++ b/src/eckit/geo/polygon/Polygon.cc @@ -0,0 +1,64 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/polygon/Polygon.h" + + +namespace eckit::geo::polygon { + + +bool Polygon::congruent(const Polygon& p) const { + if (empty()) { + return true; + } + + if (size() != p.size()) { + return false; + } + + int offset = -1; + for (int i = 0; i < size(); i++) { + if (at(i) == p.at(0)) { + offset = i; + break; + } + } + + if (offset == -1) { + return false; + } + + for (int i = 1; i < size(); i++) { + if (at((i + offset) % size()) != p.at(i)) { + return false; + } + } + return true; +} + +void Polygon::print(std::ostream& s) const { + if (empty()) { + s << "[]"; + return; + } + + char z = '['; + for (const auto& v : *this) { + s << z << v; + z = ','; + } + s << ']'; +} + + +} // namespace eckit::geo::polygon diff --git a/src/eckit/geo/polygon/Polygon.h b/src/eckit/geo/polygon/Polygon.h new file mode 100644 index 000000000..1e2b19792 --- /dev/null +++ b/src/eckit/geo/polygon/Polygon.h @@ -0,0 +1,52 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + +#include "eckit/geo/Point2.h" + + +namespace eckit::geo::polygon { + + +class Polygon : protected std::deque { +public: + using container_type = std::deque; + using container_type::value_type; + + Polygon() = default; + + Polygon(std::initializer_list l) : container_type(l) {} + + using container_type::push_back; + using container_type::push_front; + + size_t num_vertices() const { return size(); } + + const value_type& vertex(size_t idx) const { return at(idx); } + + bool sameAs(const Polygon& p) const { return *this == p; } + + bool congruent(const Polygon&) const; + + void print(std::ostream&) const; + + friend std::ostream& operator<<(std::ostream& s, const Polygon& p) { + p.print(s); + return s; + } +}; + + +} // namespace eckit::geo::polygon diff --git a/src/eckit/geo/projection/Composer.cc b/src/eckit/geo/projection/Composer.cc new file mode 100644 index 000000000..ef00afa12 --- /dev/null +++ b/src/eckit/geo/projection/Composer.cc @@ -0,0 +1,98 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/projection/Composer.h" + +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo::projection { + + +std::vector Composer::fwd_points(const Point& p) const { + if (empty()) { + return {}; + } + + std::vector points; + points.reserve(size()); + + auto q = p; + for (const auto* proj : *this) { + points.emplace_back(proj->fwd(q)); + q = points.back(); + } + + return points; +} + + +std::vector Composer::inv_points(const Point& p) const { + if (empty()) { + return {}; + } + + std::vector points; + points.reserve(size()); + + auto q = p; + for (auto proj = rbegin(); proj != rend(); ++proj) { + points.emplace_back((*proj)->inv(q)); + q = points.back(); + } + + return points; +} + + +void Composer::fill_spec(spec::Custom& custom) const { + std::vector specs; + for (const auto* proj : *this) { + specs.emplace_back(proj->spec_str()); + } + + custom.set("projections", specs); +} + + +Point Composer::fwd(const Point& p) const { + auto q = p; + for (const auto* proj : *this) { + q = proj->fwd(q); + } + return q; +} + + +Point Composer::inv(const Point& p) const { + auto q = p; + for (auto proj = rbegin(); proj != rend(); ++proj) { + q = (*proj)->inv(q); + } + return q; +} + + +Projection* Composer::compose_back(Projection* p, const Spec& spec) { + return new Composer{p, ProjectionFactory::instance().get(spec.get_string("projection")).create(spec)}; +} + + +Projection* Composer::compose_front(const Spec& spec, Projection* p) { + return new Composer{ProjectionFactory::instance().get(spec.get_string("projection")).create(spec), p}; +} + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/Composer.h b/src/eckit/geo/projection/Composer.h new file mode 100644 index 000000000..10aaf783a --- /dev/null +++ b/src/eckit/geo/projection/Composer.h @@ -0,0 +1,57 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + +#include "eckit/geo/Projection.h" + + +namespace eckit::geo::projection { + + +class Composer : public Projection, private std::deque { +public: + // -- Constructors + + explicit Composer() = default; + using deque::deque; + + // -- Methods + + using deque::clear; + using deque::emplace_back; + using deque::emplace_front; + + using deque::empty; + using deque::size; + + std::vector fwd_points(const Point&) const; + std::vector inv_points(const Point&) const; + + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; + + Point fwd(const Point&) const override; + Point inv(const Point&) const override; + + // -- Class methods + + [[nodiscard]] static Projection* compose_back(Projection*, const Spec&); + [[nodiscard]] static Projection* compose_front(const Spec&, Projection*); +}; + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/LambertAzimuthalEqualArea.cc b/src/eckit/geo/projection/LambertAzimuthalEqualArea.cc new file mode 100644 index 000000000..b06fd4d76 --- /dev/null +++ b/src/eckit/geo/projection/LambertAzimuthalEqualArea.cc @@ -0,0 +1,74 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/projection/LambertAzimuthalEqualArea.h" + +#include + +#include "eckit/geo/spec/Custom.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::projection { + + +static ProjectionBuilder PROJECTION_1("lambert_azimuthal_equal_area"); +static ProjectionBuilder PROJECTION_2("laea"); + + +LambertAzimuthalEqualArea::LambertAzimuthalEqualArea(const Spec& spec) : + LambertAzimuthalEqualArea({spec.get_double("lon_0"), spec.get_double("lat_0")}, + {spec.get_double("first_lon"), spec.get_double("first_lat")}) {} + + +LambertAzimuthalEqualArea::LambertAzimuthalEqualArea(PointLonLat centre, PointLonLat first) : + centre_(centre), + centre_r_(PointLonLatR::make_from_lonlat(centre.lon, centre.lat)), + first_(first), + first_r_(PointLonLatR::make_from_lonlat(first.lon, first.lat)), + phi0_(centre_r_.latr), + phi_(first_r_.latr), + dlam_(first_r_.lonr - centre_r_.lonr) {} + + +Point2 LambertAzimuthalEqualArea::fwd(const PointLonLat& p) const { + const auto kp = figure().R() * std::sqrt(2. / (1. + phi0_.sin * phi_.sin + phi0_.cos * phi_.cos * dlam_.cos)); + + auto x = kp * phi_.cos * dlam_.sin; + auto y = kp * (phi0_.cos * phi_.sin - phi0_.sin * phi_.cos * dlam_.cos); + + return {x, y}; +} + + +PointLonLat LambertAzimuthalEqualArea::inv(const Point2& p) const { + auto rho = std::sqrt(p.X * p.X + p.Y * p.Y); + const util::sincos_t c(2. * std::asin(rho / (2. * figure().R()))); + + return PointLonLat::make_from_lonlatr( + centre_r_.lonr + std::atan2(p.X * c.sin, rho * phi0_.cos * c.cos - p.Y * phi0_.sin * c.sin), + std::asin(c.cos * phi0_.sin + p.Y * c.sin * phi0_.cos / rho)); +} + + +void LambertAzimuthalEqualArea::fill_spec(spec::Custom& custom) const { + ProjectionOnFigure::fill_spec(custom); + + custom.set("projection", "laea"); + custom.set("lon_0", centre_.lon); + custom.set("lat_0", centre_.lat); + custom.set("lon_first", first_.lon); + custom.set("lat_first", first_.lat); +} + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/LambertAzimuthalEqualArea.h b/src/eckit/geo/projection/LambertAzimuthalEqualArea.h new file mode 100644 index 000000000..dadc35ee7 --- /dev/null +++ b/src/eckit/geo/projection/LambertAzimuthalEqualArea.h @@ -0,0 +1,92 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/projection/ProjectionOnFigure.h" +#include "eckit/geo/util/sincos.h" + + +namespace eckit::geo::projection { + + +class LambertAzimuthalEqualArea : public ProjectionOnFigure { +public: + // -- Types + // None + + // -- Exceptions + // None + + // -- Constructors + + explicit LambertAzimuthalEqualArea(const Spec&); + LambertAzimuthalEqualArea(PointLonLat centre, PointLonLat first); + + // -- Destructor + // None + + // -- Convertors + // None + + // -- Operators + // None + + // -- Methods + + Point2 fwd(const PointLonLat& p) const; + PointLonLat inv(const Point2& q) const; + + // -- Overridden methods + + inline Point fwd(const Point& p) const override { return fwd(std::get(p)); } + inline Point inv(const Point& q) const override { return inv(std::get(q)); } + + // -- Class members + // None + + // -- Class methods + // None + +protected: + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; + +private: + // -- Members + + const PointLonLat centre_; // central meridian/standard parallel [degree] + const PointLonLatR centre_r_; // central meridian/standard parallel [radian] + + const PointLonLat first_; // first point [degree] + const PointLonLatR first_r_; // first point [radian] + + const util::sincos_t phi0_; + const util::sincos_t phi_; + const util::sincos_t dlam_; + + // -- Methods + // None + + // -- Class members + // None + + // -- Class methods + // None + + // -- Friends + // None +}; + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/LambertConformalConic.cc b/src/eckit/geo/projection/LambertConformalConic.cc new file mode 100644 index 000000000..0d654c2ac --- /dev/null +++ b/src/eckit/geo/projection/LambertConformalConic.cc @@ -0,0 +1,107 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/projection/LambertConformalConic.h" + +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/geo/util.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::projection { + + +LambertConformalConic::LambertConformalConic(const Spec& spec) : + LambertConformalConic({spec.get_double("lon_0"), spec.get_double("lat_0")}, + {spec.get_double("first_lon"), spec.get_double("first_lat")}, spec.get_double("lat_1"), + spec.get_double("lat_2")) {} + + +LambertConformalConic::LambertConformalConic(PointLonLat centre, PointLonLat first, double lat_1, double lat_2) : + centre_(PointLonLat::make(centre.lon, centre.lat)), + centre_r_(PointLonLatR::make_from_lonlat(centre.lon, centre.lat)), + first_(PointLonLat::make(first.lon, first.lat)), + first_r_(PointLonLatR::make_from_lonlat(first.lon, first.lat)), + lat_1_(lat_1), + lat_1_r_(lat_1 * util::DEGREE_TO_RADIAN), + lat_2_(lat_2), + lat_2_r_(lat_2 * util::DEGREE_TO_RADIAN) { + ASSERT(!types::is_approximately_equal(figure().R(), 0.)); + + if (types::is_approximately_equal(lat_1, -lat_2)) { + throw ProjectionProblem( + "LambertConformalConic: cannot have equal latitudes for standard parallels on opposite sides of equator", + Here()); + } + + n_ = types::is_approximately_equal(lat_1, lat_2) + ? std::sin(lat_1_r_) + : std::log(std::cos(lat_1_r_) / std::cos(lat_2_r_)) + / std::log(std::tan(M_PI_4 + lat_2_r_ / 2.) / std::tan(M_PI_4 + lat_1_r_ / 2.)); + + if (types::is_approximately_equal(n_, 0.)) { + throw ProjectionProblem("LambertConformalConic: cannot corretly calculate n_", Here()); + } + + f_ = (std::cos(lat_1_r_) * std::pow(std::tan(M_PI_4 + lat_1_r_ / 2.), n_)) / n_; + rho0_bare_ = f_ * std::pow(std::tan(M_PI_4 + centre_r_.latr / 2.), -n_); +} + + +Point2 LambertConformalConic::fwd(const PointLonLat& p) const { + auto q = PointLonLatR::make_from_lonlat(p.lon, p.lat); + + auto rho = figure().R() * f_ * std::pow(std::tan(M_PI_4 + q.latr / 2.), -n_); + auto rho0 = figure().R() * rho0_bare_; // scaled + auto dlam = q.lonr - centre_r_.lonr; + + return {rho * std::sin(n_ * dlam), rho0 - rho * std::cos(n_ * dlam)}; +} + + +PointLonLat LambertConformalConic::inv(const Point2& p) const { + auto x = p.X / figure().R(); + auto y = rho0_bare_ - p.Y / figure().R(); + + if (auto rho = std::hypot(x, y); !types::is_approximately_equal(rho, 0.)) { + if (n_ < 0.) { + rho = -rho; + x = -x; + y = -y; + } + auto lonr = std::atan2(x, y) / n_ + centre_r_.lonr; + auto latr = 2. * std::atan(std::pow(f_ / rho, 1. / n_)) - M_PI_2; + + return PointLonLat::make_from_lonlatr(lonr, latr); + } + + return PointLonLat::make(0., n_ > 0 ? PointLonLat::RIGHT_ANGLE : -PointLonLat::RIGHT_ANGLE); +} + + +void LambertConformalConic::fill_spec(spec::Custom& custom) const { + ProjectionOnFigure::fill_spec(custom); + + custom.set("projection", "lcc"); + custom.set("lon_0", centre_.lon); + custom.set("lat_0", centre_.lat); + custom.set("first_lon", first_.lon); + custom.set("first_lat", first_.lat); + custom.set("lat_1", lat_1_); + custom.set("lat_2", lat_1_); +} + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/LambertConformalConic.h b/src/eckit/geo/projection/LambertConformalConic.h new file mode 100644 index 000000000..bc9c7128f --- /dev/null +++ b/src/eckit/geo/projection/LambertConformalConic.h @@ -0,0 +1,104 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/projection/ProjectionOnFigure.h" + + +namespace eckit::geo::projection { + + +/** + * @brief LambertConformalConic projection + * @ref Map Projections: A Working Manual, John P. Snyder (1987) + * @ref Wolfram MathWorld (http://mathworld.wolfram.com/LambertConformalConicProjection.html) + */ +class LambertConformalConic : public ProjectionOnFigure { +public: + // -- Types + // None + + // -- Exceptions + // None + + // -- Constructors + + explicit LambertConformalConic(const Spec&); + + LambertConformalConic(PointLonLat centre, PointLonLat first, double lat_1, double lat_2); + LambertConformalConic(PointLonLat centre, PointLonLat first, double lat_1) : + LambertConformalConic(centre, first, lat_1, lat_1) {} + + // -- Destructor + // None + + // -- Convertors + // None + + // -- Operators + // None + + // -- Methods + + Point2 fwd(const PointLonLat& p) const; + PointLonLat inv(const Point2& q) const; + + // -- Overridden methods + + inline Point fwd(const Point& p) const override { return fwd(std::get(p)); } + inline Point inv(const Point& q) const override { return inv(std::get(q)); } + + // -- Class members + // None + + // -- Class methods + // None + +protected: + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; + +private: + // -- Members + + const PointLonLat centre_; // central meridian/parallel [degree] + const PointLonLatR centre_r_; // central meridian/parallel [radian] + + const PointLonLat first_; // first point [degree] + const PointLonLatR first_r_; // first point [radian] + + const double lat_1_; + const double lat_1_r_; + const double lat_2_; + const double lat_2_r_; + + double n_; + double f_; + double rho0_bare_; + + // -- Methods + // None + + // -- Class members + // None + + // -- Class methods + // None + + // -- Friends + // None +}; + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/LonLatToXYZ.cc b/src/eckit/geo/projection/LonLatToXYZ.cc new file mode 100644 index 000000000..e868fccf4 --- /dev/null +++ b/src/eckit/geo/projection/LonLatToXYZ.cc @@ -0,0 +1,78 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/projection/LonLatToXYZ.h" + +#include "eckit/geo/Spec.h" +#include "eckit/geo/figure/OblateSpheroid.h" +#include "eckit/geo/figure/Sphere.h" +#include "eckit/geo/geometry/OblateSpheroid.h" +#include "eckit/geo/geometry/Sphere.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::projection { + + +static ProjectionBuilder PROJECTION("ll_to_xyz"); + + +LonLatToXYZ::LonLatToXYZ(Figure* figure_ptr) : ProjectionOnFigure(figure_ptr) { + struct LonLatToSphereXYZ final : Implementation { + const double R; + + explicit LonLatToSphereXYZ(double _R) : R(_R) {} + Point3 operator()(const PointLonLat& p) const override { + return geometry::Sphere::convertSphericalToCartesian(R, p, 0.); + } + PointLonLat operator()(const Point3& q) const override { + return geometry::Sphere::convertCartesianToSpherical(R, q); + } + }; + + struct LonLatToSpheroidXYZ final : Implementation { + const double a; + const double b; + + explicit LonLatToSpheroidXYZ(double _a, double _b) : a(_a), b(_b) {} + Point3 operator()(const PointLonLat& p) const override { + return geometry::OblateSpheroid::convertSphericalToCartesian(a, b, p, 0.); + } + PointLonLat operator()(const Point3& q) const override { NOTIMP; } + }; + + impl_.reset(types::is_approximately_equal(figure().eccentricity(), 0.) + ? static_cast(new LonLatToSphereXYZ(figure().R())) + : new LonLatToSpheroidXYZ(figure().a(), figure().b())); +} + + +LonLatToXYZ::LonLatToXYZ(double R) : LonLatToXYZ(R, R) {} + + +LonLatToXYZ::LonLatToXYZ(double a, double b) : + LonLatToXYZ(types::is_approximately_equal(a, b) ? static_cast(new geo::figure::Sphere(a)) + : new geo::figure::OblateSpheroid(a, b)) {} + + +LonLatToXYZ::LonLatToXYZ(const Spec& spec) : LonLatToXYZ(FigureFactory::build(spec)) {} + + +void LonLatToXYZ::fill_spec(spec::Custom& custom) const { + ProjectionOnFigure::fill_spec(custom); + + custom.set("projection", "ll_to_xyz"); +} + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/LonLatToXYZ.h b/src/eckit/geo/projection/LonLatToXYZ.h new file mode 100644 index 000000000..f7e9dd520 --- /dev/null +++ b/src/eckit/geo/projection/LonLatToXYZ.h @@ -0,0 +1,72 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + +#include "eckit/geo/projection/ProjectionOnFigure.h" + + +namespace eckit::geo::projection { + + +/// Calculate coordinates of a point on a sphere or spheroid, in [x, y, z] +class LonLatToXYZ : public ProjectionOnFigure { +public: + // -- Constructors + + explicit LonLatToXYZ(Figure* = nullptr); + + explicit LonLatToXYZ(double R); + explicit LonLatToXYZ(double a, double b); + + explicit LonLatToXYZ(const Spec&); + + // -- Methods + + inline Point3 fwd(const PointLonLat& p) const { return (*impl_)(p); } + inline PointLonLat inv(const Point3& q) const { return (*impl_)(q); } + + // -- Overridden methods + + inline Point fwd(const Point& p) const override { return (*impl_)(std::get(p)); } + inline Point inv(const Point& q) const override { return (*impl_)(std::get(q)); } + +protected: + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; + +private: + // -- Types + + struct Implementation { + Implementation() = default; + virtual ~Implementation() = default; + + Implementation(const Implementation&) = delete; + Implementation(Implementation&&) = delete; + void operator=(const Implementation&) = delete; + void operator=(Implementation&&) = delete; + + virtual Point3 operator()(const PointLonLat&) const = 0; + virtual PointLonLat operator()(const Point3&) const = 0; + }; + + // -- Members + + std::unique_ptr impl_; +}; + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/Mercator.cc b/src/eckit/geo/projection/Mercator.cc new file mode 100644 index 000000000..e7a48ea9c --- /dev/null +++ b/src/eckit/geo/projection/Mercator.cc @@ -0,0 +1,121 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/projection/Mercator.h" + +#include +#include + +#include "eckit/geo/spec/Custom.h" +#include "eckit/geo/util.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::projection { + + +static ProjectionBuilder PROJECTION_1("mercator"); +static ProjectionBuilder PROJECTION_2("merc"); + + +Mercator::Mercator(PointLonLat centre, PointLonLat first, Figure* figure_ptr) : + ProjectionOnFigure(figure_ptr), + centre_(PointLonLat::make(centre.lon, centre.lat, -PointLonLat::FLAT_ANGLE)), + first_(first), + eps_(1e-10), + max_iter_(15) { + // Map Projections: A Working Manual, John P. Snyder (1987) + // - Equation (7-9) to calculate phi iteratively + // - Equation (15-11) to calculate t + + if (types::is_approximately_equal(first.lat, PointLonLat::RIGHT_ANGLE) + || types::is_approximately_equal(first.lat, -PointLonLat::RIGHT_ANGLE)) { + throw ProjectionProblem("Mercator: projection cannot be calculated at the poles", Here()); + } + + auto lam0 = util::DEGREE_TO_RADIAN * centre_.lon; + auto phi0 = util::DEGREE_TO_RADIAN * centre_.lat; + auto lam1 = util::DEGREE_TO_RADIAN * first.lon; + auto phi1 = util::DEGREE_TO_RADIAN * first.lat; + + e_ = figure().eccentricity(); + lam0_ = lam0; + + m_ = figure().a() * std::cos(phi0) / (std::sqrt(1. - e_ * e_ * std::sin(phi0) * std::sin(phi0))); + ASSERT(!types::is_approximately_equal(m_, 0.)); + + w_ = 1. / m_; + x0_ = m_ * (lam0_ - lam1); + y0_ = m_ + * std::log(std::tan(M_PI_4 - 0.5 * phi1) + / std::pow(((1. - e_ * std::sin(phi1)) / (1. + e_ * std::sin(phi1))), 0.5 * e_)); + + ASSERT(types::is_approximately_equal(phi1, calculate_phi(std::exp(y0_ * w_)), eps_)); +} + + +Mercator::Mercator(const Spec& spec) : + Mercator({spec.get_double("lon_0"), spec.get_double("lat_ts")}, + {spec.get_double("first_lon"), spec.get_double("first_lat")}, FigureFactory::build(spec)) {} + + +double Mercator::calculate_phi(double t) const { + auto phi = M_PI_2 - 2 * std::atan(t); + + for (size_t i = 0; i <= max_iter_; i++) { + auto es = e_ * std::sin(phi); + auto dphi = M_PI_2 - 2 * std::atan(t * (std::pow(((1. - es * es) / (1. + es * es)), 0.5 * e_))) - phi; + + phi += dphi; + if (types::is_approximately_equal(dphi, 0., eps_)) { + return phi; + } + } + + throw SeriousBug("Mercator: phi calculation failed to converge", Here()); +} + + +Point2 Mercator::fwd(const PointLonLat& p) const { + auto phi = util::DEGREE_TO_RADIAN * p.lat; + auto lam = util::DEGREE_TO_RADIAN * p.lon; + auto s = std::sin(phi); + + return { + x0_ + m_ * (lam - lam0_), + types::is_approximately_equal(s, 1.) ? std::numeric_limits::infinity() + : types::is_approximately_equal(s, -1.) + ? -std::numeric_limits::infinity() + : y0_ - m_ * std::log(std::tan(M_PI_4 - 0.5 * phi) / std::pow(((1. - e_ * s) / (1. + e_ * s)), 0.5 * e_))}; +} + + +PointLonLat Mercator::inv(const Point2& q) const { + return PointLonLat::make(util::RADIAN_TO_DEGREE * (lam0_ + (q.X - x0_) * w_), + util::RADIAN_TO_DEGREE * calculate_phi(std::exp(-(q.Y - y0_) * w_))); +} + + +void Mercator::fill_spec(spec::Custom& custom) const { + ProjectionOnFigure::fill_spec(custom); + + custom.set("projection", "mercator"); + if (!types::is_approximately_equal(centre_.lat, 0.)) { + custom.set("lat_ts", centre_.lat); + } + if (!types::is_approximately_equal(centre_.lon, 0.)) { + custom.set("lon_0", centre_.lon); + } +} + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/Mercator.h b/src/eckit/geo/projection/Mercator.h new file mode 100644 index 000000000..45d63fcd1 --- /dev/null +++ b/src/eckit/geo/projection/Mercator.h @@ -0,0 +1,68 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/projection/ProjectionOnFigure.h" + + +namespace eckit::geo::projection { + + +/// Calculate coordinates of a point on a rotated sphere given new location of South Pole (vector) and angle +class Mercator : public ProjectionOnFigure { +public: + // -- Constructors + + explicit Mercator(PointLonLat centre, PointLonLat first = {0, 0}, Figure* = nullptr); + + explicit Mercator(const Spec&); + + // -- Methods + + Point2 fwd(const PointLonLat& p) const; + PointLonLat inv(const Point2& q) const; + + // -- Overridden methods + + inline Point fwd(const Point& p) const override { return fwd(std::get(p)); } + inline Point inv(const Point& q) const override { return inv(std::get(q)); } + +protected: + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; + +private: + // -- Members + + const PointLonLat centre_; // angle [degree] between Eastward direction and the Equator, range [0, 90], latitude + // [degree] of projection intersecting ellipsoid + const PointLonLat first_; + + const double eps_; + const size_t max_iter_; + + double lam0_; + double x0_; + double y0_; + double e_; + double m_; + double w_; + + // -- Methods + + double calculate_phi(double t) const; +}; + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/None.cc b/src/eckit/geo/projection/None.cc new file mode 100644 index 000000000..2f6e81cbf --- /dev/null +++ b/src/eckit/geo/projection/None.cc @@ -0,0 +1,24 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/projection/None.h" + +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo::projection { + + +static ProjectionBuilder PROJECTION("none"); + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/None.h b/src/eckit/geo/projection/None.h new file mode 100644 index 000000000..fd60b05e2 --- /dev/null +++ b/src/eckit/geo/projection/None.h @@ -0,0 +1,37 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/Projection.h" + + +namespace eckit::geo::projection { + + +class None : public Projection { +public: + // -- Constructors + + explicit None() = default; + explicit None(const Spec&) {} + + // -- Overridden methods + + inline Point fwd(const Point& p) const override { return p; } + inline Point inv(const Point& q) const override { return q; } + + void fill_spec(spec::Custom&) const override {} +}; + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/PROJ.cc b/src/eckit/geo/projection/PROJ.cc new file mode 100644 index 000000000..c0aa7b482 --- /dev/null +++ b/src/eckit/geo/projection/PROJ.cc @@ -0,0 +1,257 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/projection/PROJ.h" + +#include + +#include +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/Figure.h" +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo::projection { + + +static ProjectionBuilder PROJECTION("proj"); + + +namespace { + + +constexpr auto CTX = PJ_DEFAULT_CTX; +static const std::string DEFAULT = "EPSG:4326"; // WGS84, latitude/longitude coordinate system + + +struct pj_t : std::unique_ptr { + explicit pj_t(element_type* ptr) : unique_ptr(ptr, &proj_destroy) {} +}; + + +struct ctx_t : std::unique_ptr { + explicit ctx_t(element_type* ptr) : unique_ptr(ptr, &proj_context_destroy) {} +}; + + +struct Convert { + Convert() = default; + virtual ~Convert() = default; + + Convert(const Convert&) = delete; + Convert(Convert&&) = delete; + void operator=(const Convert&) = delete; + void operator=(Convert&&) = delete; + + virtual PJ_COORD to_coord(const Point&) const = 0; + virtual Point to_point(const PJ_COORD&) const = 0; +}; + + +struct LonLat final : Convert { + PJ_COORD to_coord(const Point& p) const final { + const auto& q = std::get(p); + return proj_coord(q.lon, q.lat, 0, 0); + } + + Point to_point(const PJ_COORD& c) const final { return PointLonLat::make(c.enu.e, c.enu.n, lon_minimum_); } + + explicit LonLat(double lon_minimum) : lon_minimum_(lon_minimum) {} + const double lon_minimum_; +}; + + +struct XY final : Convert { + PJ_COORD to_coord(const Point& p) const final { + const auto& q = std::get(p); + return proj_coord(q.X, q.Y, 0, 0); + } + + Point to_point(const PJ_COORD& c) const final { return Point2{c.xy.x, c.xy.y}; } +}; + + +struct XYZ final : Convert { + PJ_COORD to_coord(const Point& p) const final { + const auto& q = std::get(p); + return proj_coord(q.X, q.Y, q.Z, 0); + } + + Point to_point(const PJ_COORD& c) const final { return Point3{c.xy.x, c.xy.y, c.xyz.z}; } +}; + + +} // namespace + + +struct PROJ::Implementation { + Implementation(PJ* pj_ptr, PJ_CONTEXT* pjc_ptr, Convert* source_ptr, Convert* target_ptr) : + proj_(pj_ptr), ctx_(pjc_ptr), source_(source_ptr), target_(target_ptr) { + ASSERT(proj_); + ASSERT(source_); + ASSERT(target_); + } + + inline Point fwd(const Point& p) const { + return target_->to_point(proj_trans(proj_.get(), PJ_FWD, source_->to_coord(p))); + } + + inline Point inv(const Point& p) const { + return source_->to_point(proj_trans(proj_.get(), PJ_INV, target_->to_coord(p))); + } + +private: + const pj_t proj_; + const ctx_t ctx_; + const std::unique_ptr source_; + const std::unique_ptr target_; +}; + + +PROJ::PROJ(const std::string& source, const std::string& target, double lon_minimum) : + source_(source), target_(target) { + ASSERT(!source.empty()); + + auto make_convert = [lon_minimum](const std::string& string) -> Convert* { + pj_t identity(proj_create_crs_to_crs(CTX, string.c_str(), string.c_str(), nullptr)); + pj_t crs(proj_get_target_crs(CTX, identity.get())); + pj_t cs(proj_crs_get_coordinate_system(CTX, crs.get())); + ASSERT(cs); + + auto type = proj_cs_get_type(CTX, cs.get()); + auto dim = proj_cs_get_axis_count(CTX, cs.get()); + + return type == PJ_CS_TYPE_CARTESIAN && dim == 3 ? static_cast(new XYZ) + : type == PJ_CS_TYPE_CARTESIAN && dim == 2 ? static_cast(new XY) + : type == PJ_CS_TYPE_ELLIPSOIDAL ? static_cast(new LonLat(lon_minimum)) + : type == PJ_CS_TYPE_SPHERICAL ? static_cast(new LonLat(lon_minimum)) + : NOTIMP; + }; + + // projection, normalised + auto ctx = PJ_DEFAULT_CTX; + + implementation_ = std::make_unique( + proj_normalize_for_visualization(ctx, proj_create_crs_to_crs(ctx, source_.c_str(), target_.c_str(), nullptr)), + ctx, make_convert(source_), make_convert(target_)); + ASSERT(implementation_); +} + + +PROJ::PROJ(const Spec& spec) : + PROJ(spec.get_string("source", spec.get_string("proj", DEFAULT)), spec.get_string("target", DEFAULT), + spec.get_double("lon_minimum", 0)) {} + + +Figure* PROJ::make_figure() const { + pj_t identity(proj_create_crs_to_crs(CTX, target_.c_str(), target_.c_str(), nullptr)); + + pj_t crs(proj_get_target_crs(CTX, identity.get())); + pj_t ellipsoid(proj_get_ellipsoid(CTX, crs.get())); + ASSERT(ellipsoid); + + double a = 0; + double b = 0; + ASSERT(proj_ellipsoid_get_parameters(CTX, ellipsoid.get(), &a, &b, nullptr, nullptr)); + ASSERT(0 < b && b <= a); + + return FigureFactory::build(spec::Custom{{{"a", a}, {"b", b}}}); +} + + +Point PROJ::fwd(const Point& p) const { + return implementation_->fwd(p); +} + + +Point PROJ::inv(const Point& q) const { + return implementation_->inv(q); +} + + +std::string PROJ::proj_str(const spec::Custom& custom) { + using key_value_type = std::pair; + + struct key_value_compare { + bool operator()(const key_value_type& a, const key_value_type& b) const { + if (a.first != b.first) { + // keys that come first in string + for (const std::string& key : {"proj"}) { + if (a.first == key || b.first == key) { + return a.first == key; + } + } + + // keys that come last in string + for (const std::string& key : {"R", "a", "b"}) { + if (a.first == key || b.first == key) { + return b.first == key; + } + } + } + + return a < b; + }; + }; + + static const std::map KEYS{ + {"projection", "proj"}, + {"figure", "ellps"}, + {"r", "R"}, + }; + + static const std::map VALUES{ + {"mercator", "merc"}, + {"reverse_mercator", "merc"}, + {"grs80", "GRS80"}, + {"wgs84", "WGS84"}, + }; + + auto rename = [](const std::map& map, const std::string& key) { + const auto it = map.find(key); + return it != map.end() ? it->second : key; + }; + + std::set set; + for (const auto& [k, v] : custom.container()) { + if (const auto& key = rename(KEYS, k); !key.empty()) { + const auto& value = rename(VALUES, to_string(v)); + set.emplace(key, value); + } + } + + std::string str; + const auto* sep = "+"; + for (const auto& [key, value] : set) { + str += sep + key + "=" + value; + sep = " +"; + } + + return str; +} + + +void PROJ::fill_spec(spec::Custom& custom) const { + custom.set("projection", "proj"); + if (source_ != DEFAULT) { + custom.set("source", source_); + } + if (target_ != DEFAULT) { + custom.set("target", target_); + } +} + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/PROJ.h b/src/eckit/geo/projection/PROJ.h new file mode 100644 index 000000000..1cb1d9353 --- /dev/null +++ b/src/eckit/geo/projection/PROJ.h @@ -0,0 +1,65 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + +#include "eckit/geo/Projection.h" + + +namespace eckit::geo::projection { + + +/// Calculate coordinates using PROJ +class PROJ : public Projection { +public: + // -- Constructors + + PROJ(const std::string& source, const std::string& target, double lon_minimum = 0.); + explicit PROJ(const Spec&); + + // -- Methods + + const std::string& source() const { return source_; } + const std::string& target() const { return target_; } + + // -- Overridden methods + + Point fwd(const Point&) const override; + Point inv(const Point&) const override; + + [[nodiscard]] Figure* make_figure() const override; + + // -- Class methods + + static std::string proj_str(const spec::Custom&); + +private: + // -- Types + + struct Implementation; + + // -- Members + + std::unique_ptr implementation_; + + const std::string source_; + const std::string target_; + + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; +}; + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/PolarStereographic.cc b/src/eckit/geo/projection/PolarStereographic.cc new file mode 100644 index 000000000..6def4696a --- /dev/null +++ b/src/eckit/geo/projection/PolarStereographic.cc @@ -0,0 +1,79 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/projection/PolarStereographic.h" + +#include + +#include "eckit/geo/spec/Custom.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::projection { + + +PolarStereographic::PolarStereographic(const Spec& spec) : + PolarStereographic({spec.get_double("lon_0"), spec.get_double("lat_0")}, + {spec.get_double("first_lon"), spec.get_double("first_lat")}) {} + + +PolarStereographic::PolarStereographic(PointLonLat centre, PointLonLat first, Figure* figure_ptr) : + ProjectionOnFigure(figure_ptr), + centre_(PointLonLat::make(centre.lon, centre.lat)), + centre_r_(PointLonLatR::make_from_lonlat(centre.lon, centre.lat)), + first_(first), + first_r_(PointLonLatR::make_from_lonlat(first.lon, first.lat)), + sign_(centre_.lat < 0. ? -1. : 1.), + F_(types::is_approximately_equal(centre_.lat, PointLonLat::RIGHT_ANGLE, PointLonLat::EPS) + || types::is_approximately_equal(centre_.lat, -PointLonLat::RIGHT_ANGLE, PointLonLat::EPS) + ? 0.5 + : std::tan(0.5 * (M_PI_2 - sign_ * centre_r_.latr)) / std::cos(sign_ * centre_r_.latr)) { + auto z = fwd(first_); + x0_ = z.X; + y0_ = z.Y; +} + + +Point2 PolarStereographic::fwd(const PointLonLat& q) const { + auto p = PointLonLatR::make_from_lonlat(q.lon, q.lat); + + auto a = sign_ * (p.lonr - centre_r_.lonr); + auto tsf = std::tan(0.5 * (M_PI_2 - sign_ * p.latr)); + auto height = figure().R() * tsf / F_; + + return {-sign_ * height * std::sin(a), sign_ * height * std::cos(a)}; +} + + +PointLonLat PolarStereographic::inv(const Point2& q) const { + Point2 p{q.X - x0_, q.Y - y0_}; + + auto rh = std::sqrt(p.X * p.X + p.Y * p.Y); + auto tsi = rh / figure().R() * F_; + + return PointLonLat::make_from_lonlatr(sign_ * std::atan2(sign_ * p.X, -sign_ * p.Y) + centre_r_.lonr, + sign_ * (M_PI_2 - 2 * std::atan(tsi))); +} + + +void PolarStereographic::fill_spec(spec::Custom& custom) const { + ProjectionOnFigure::fill_spec(custom); + + custom.set("projection", "stere"); + custom.set("lon_0", centre_.lon); + custom.set("lat_0", centre_.lat); + custom.set("lon_first", first_.lon); + custom.set("lat_first", first_.lat); +} + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/PolarStereographic.h b/src/eckit/geo/projection/PolarStereographic.h new file mode 100644 index 000000000..bbc1e4892 --- /dev/null +++ b/src/eckit/geo/projection/PolarStereographic.h @@ -0,0 +1,92 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/projection/ProjectionOnFigure.h" + + +namespace eckit::geo::projection { + + +class PolarStereographic : public ProjectionOnFigure { +public: + // -- Types + // None + + // -- Exceptions + // None + + // -- Constructors + + explicit PolarStereographic(const Spec&); + PolarStereographic(PointLonLat centre, PointLonLat first = {0, 0}, Figure* = nullptr); + + // -- Destructor + // None + + // -- Convertors + // None + + // -- Operators + // None + + // -- Methods + + Point2 fwd(const PointLonLat& p) const; + PointLonLat inv(const Point2& q) const; + + // -- Overridden methods + + inline Point fwd(const Point& p) const override { return fwd(std::get(p)); } + inline Point inv(const Point& q) const override { return inv(std::get(q)); } + + // -- Class members + // None + + // -- Class methods + // None + +protected: + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; + +private: + // -- Members + + const PointLonLat centre_; // projection centre [degree] + const PointLonLatR centre_r_; // projection centre [radian] + + const PointLonLat first_; // first point [degree] + const PointLonLatR first_r_; // first point [radian] + + const double sign_; + const double F_; + double x0_; + double y0_; + + // -- Methods + // None + + // -- Class members + // None + + // -- Class methods + // None + + // -- Friends + // None +}; + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/ProjectionOnFigure.cc b/src/eckit/geo/projection/ProjectionOnFigure.cc new file mode 100644 index 000000000..49e1953b6 --- /dev/null +++ b/src/eckit/geo/projection/ProjectionOnFigure.cc @@ -0,0 +1,42 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/projection/ProjectionOnFigure.h" + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/figure/Earth.h" +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo::projection { + + +ProjectionOnFigure::ProjectionOnFigure(const Spec&) {} + + +ProjectionOnFigure::ProjectionOnFigure(Figure* figure_ptr) : + figure_(figure_ptr != nullptr ? figure_ptr : new figure::Earth) { + ASSERT(figure_); +} + + +Figure* ProjectionOnFigure::make_figure() const { + return FigureFactory::build(spec::Custom{{"a", figure_->a()}, {"b", figure_->b()}}); +} + + +void ProjectionOnFigure::fill_spec(spec::Custom& custom) const { + figure_->fill_spec(custom); +} + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/ProjectionOnFigure.h b/src/eckit/geo/projection/ProjectionOnFigure.h new file mode 100644 index 000000000..348cd4f14 --- /dev/null +++ b/src/eckit/geo/projection/ProjectionOnFigure.h @@ -0,0 +1,44 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + +#include "eckit/geo/Figure.h" +#include "eckit/geo/Projection.h" + + +namespace eckit::geo::projection { + + +class ProjectionOnFigure : public Projection { +public: + // -- Overridden methods + + [[nodiscard]] Figure* make_figure() const override; + void fill_spec(spec::Custom&) const override; + +protected: + // -- Constructors + + explicit ProjectionOnFigure(const Spec&); + explicit ProjectionOnFigure(Figure* = nullptr); + +private: + // -- Members + + std::shared_ptr
figure_; +}; + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/Reverse.h b/src/eckit/geo/projection/Reverse.h new file mode 100644 index 000000000..96231ad4c --- /dev/null +++ b/src/eckit/geo/projection/Reverse.h @@ -0,0 +1,55 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/Projection.h" +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo::projection { + + +/** + * @brief Reverse class + * @details Used to reverse the forward and inverse methods of a projection. + */ +template +class Reverse : protected P { +public: + // -- Constructors + + using P::P; + + // -- Methods + + using P::figure; + using P::make_figure; + + using P::spec; + using P::spec_str; + + // -- Overridden methods + + inline Point fwd(const Point& p) const override { return P::inv(p); } + inline Point inv(const Point& p) const override { return P::fwd(p); } + +private: + // -- Overridden methods + + void fill_spec(spec::Custom& custom) const override { + P::fill_spec(custom); + custom.set("projection", "reverse_" + custom.get_string("projection")); + } +}; + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/Rotation.cc b/src/eckit/geo/projection/Rotation.cc new file mode 100644 index 000000000..c0fe1a944 --- /dev/null +++ b/src/eckit/geo/projection/Rotation.cc @@ -0,0 +1,142 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/projection/Rotation.h" + +#include +#include + +#include "eckit/geo/geometry/UnitSphere.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/geo/util.h" +#include "eckit/maths/Matrix3.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::projection { + + +static ProjectionBuilder PROJECTION("rotation"); + + +Rotation::Rotation(const PointLonLat& p, double angle) : Rotation(p.lon, p.lat, angle) {} + + +Rotation::Rotation(double south_pole_lon, double south_pole_lat, double angle) : + south_pole_(PointLonLat::make(south_pole_lon, south_pole_lat)), angle_(angle), rotated_(true) { + using M = maths::Matrix3; + + struct NonRotated final : Implementation { + PointLonLat operator()(const PointLonLat& p) const override { return p; } + }; + + struct RotationAngle final : Implementation { + explicit RotationAngle(double angle) : angle_(angle) {} + PointLonLat operator()(const PointLonLat& p) const override { return {p.lon + angle_, p.lat}; } + const double angle_; + }; + + struct RotationMatrix final : Implementation { + explicit RotationMatrix(M&& R) : R_(R) {} + PointLonLat operator()(const PointLonLat& p) const override { + return geometry::UnitSphere::convertCartesianToSpherical( + R_ * geometry::UnitSphere::convertSphericalToCartesian(p)); + } + const M R_; + }; + + const auto alpha = util::DEGREE_TO_RADIAN * angle; + const auto theta = util::DEGREE_TO_RADIAN * -(south_pole_lat + 90.); + const auto phi = util::DEGREE_TO_RADIAN * -south_pole_lon; + + const auto ca = std::cos(alpha); + const auto ct = std::cos(theta); + const auto cp = std::cos(phi); + + if (types::is_approximately_equal(ct, 1., PointLonLat::EPS * util::DEGREE_TO_RADIAN)) { + angle_ = PointLonLat::normalise_angle_to_minimum(angle_ - south_pole_lon, -PointLonLat::FLAT_ANGLE); + rotated_ = !types::is_approximately_equal(angle_, 0., PointLonLat::EPS); + + fwd_.reset(rotated_ ? static_cast(new RotationAngle(-angle)) : new NonRotated); + inv_.reset(rotated_ ? static_cast(new RotationAngle(angle)) : new NonRotated); + return; + } + + // FIXME this supports either angle-based or matrix-based rotation (but not both); + // Implementing as Euler angles rotation matrix (ideal, but reordering Rz Ry Ra) changes the existing unit test + + const auto sa = std::sin(alpha); + const auto st = std::sin(theta); + const auto sp = std::sin(phi); + + // Rotate: rotate by α, then ϑ (y-axis, along the rotated Greenwich meridian), then φ (z-axis) + // q = Rz Ry Ra p = [ cosφ sinφ ] [ cosϑ sinϑ ] [ cosα sinα ] p + // [ -sinφ cosφ ] [ 1 ] [ -sinα cosα ] + // [ 1 ] [ -sinϑ cosϑ ] [ 1 ] + fwd_ = std::make_shared(M{ca * cp * ct - sa * sp, sa * cp * ct + ca * sp, + cp * st, // + -sa * cp - ca * ct * sp, ca * cp - sa * ct * sp, + -sp * st, // + -ca * st, -sa * st, ct}); + + // Un-rotate (rotate by -φ, -ϑ, -α): + // p = Ra Ry Rz q = [ cosα -sinα ] [ cosϑ -sinϑ ] [ cosφ -sinφ ] q + // [ sinα cosα ] [ 1 ] [ sinφ cosφ ] + // [ 1 ] [ sinϑ cosϑ ] [ 1 ] + inv_ = std::make_shared(M{ca * cp * ct - sa * sp, -sa * cp - ca * ct * sp, + -ca * st, // + sa * cp * ct + ca * sp, ca * cp - sa * ct * sp, + -sa * st, // + cp * st, -sp * st, ct}); + + angle_ = PointLonLat::normalise_angle_to_minimum(angle_, -PointLonLat::FLAT_ANGLE); +} + + +Rotation* Rotation::make_from_spec(const Spec& spec) { + double angle = 0.; + spec.get("rotation_angle", angle); + + auto lon = SOUTH_POLE.lon; + auto lat = SOUTH_POLE.lat; + if (std::vector r{lon, lat}; spec.get("rotation", r)) { + ASSERT_MSG(r.size() == 2, "Rotation: expected 'rotation' as a list of size 2"); + lon = r[0]; + lat = r[1]; + } + else { + ASSERT_MSG(spec.get("south_pole_lon", lon) == spec.get("south_pole_lat", lat), + "Rotation: expected 'south_pole_lon' and 'south_pole_lat'"); + } + + auto* r = new Rotation{lon, lat, angle}; + if (!r->rotated()) { + delete r; + r = nullptr; + } + + return r; +} + + +void Rotation::fill_spec(spec::Custom& custom) const { + if (!points_equal(SOUTH_POLE, south_pole_)) { + custom.set("rotation", std::vector{south_pole_.lon, south_pole_.lat}); + } + if (!types::is_approximately_equal(angle_, 0., PointLonLat::EPS)) { + custom.set("rotation_angle", angle_); + } + // custom.set("projection", "rotation"); // it's a common projection (?) +} + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/Rotation.h b/src/eckit/geo/projection/Rotation.h new file mode 100644 index 000000000..965cf1543 --- /dev/null +++ b/src/eckit/geo/projection/Rotation.h @@ -0,0 +1,92 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + +#include "eckit/geo/Projection.h" + + +namespace eckit::geo::projection { + + +/// Calculate coordinates of a point on a rotated sphere given new location of South Pole (vector) and angle +class Rotation : public Projection { +public: + // -- Constructors + + explicit Rotation(const Spec& spec) : Rotation(*std::unique_ptr(make_from_spec(spec))) {} + + Rotation(const PointLonLat& = SOUTH_POLE, double angle = 0); + Rotation(double south_pole_lon, double south_pole_lat, double angle = 0); + + Rotation(const Rotation&) = default; + Rotation(Rotation&&) = default; + + // -- Destructor + + ~Rotation() override = default; + + // -- Operators + + Rotation& operator=(const Rotation&) = default; + Rotation& operator=(Rotation&&) = default; + + // -- Methods + + bool rotated() const { return rotated_; } + + inline PointLonLat fwd(const PointLonLat& p) const { return (*fwd_)(p); } + inline PointLonLat inv(const PointLonLat& q) const { return (*inv_)(q); } + + // -- Overridden methods + + inline Point fwd(const Point& p) const override { return fwd(std::get(p)); } + inline Point inv(const Point& q) const override { return inv(std::get(q)); } + + // -- Class methods + + [[nodiscard]] static Rotation* make_from_spec(const Spec&); + +protected: + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; + +private: + // -- Types + + struct Implementation { + Implementation() = default; + virtual ~Implementation() = default; + + Implementation(const Implementation&) = delete; + Implementation(Implementation&&) = delete; + void operator=(const Implementation&) = delete; + void operator=(Implementation&&) = delete; + + virtual PointLonLat operator()(const PointLonLat&) const = 0; + }; + + // -- Members + + std::shared_ptr fwd_; + std::shared_ptr inv_; + + PointLonLat south_pole_; + double angle_; + bool rotated_; +}; + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/SpaceView.cc b/src/eckit/geo/projection/SpaceView.cc new file mode 100644 index 000000000..ea78463a9 --- /dev/null +++ b/src/eckit/geo/projection/SpaceView.cc @@ -0,0 +1,117 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/projection/SpaceView.h" + +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/geo/util.h" + + +namespace eckit::geo::projection { + + +SpaceView::SpaceView(const Spec&) { + // Orthographic not supported. This happens when Nr (camera altitude) is missing + + // if (latOfSubSatellitePointInDegrees != 0.0) { + // grib_context_log(h->context, GRIB_LOG_ERROR, + // "%s: Key %s must be 0 (satellite must be located in the equator plane)", ITER, + // sLatOfSubSatellitePointInDegrees); + // return GRIB_GEOCALCULUS_PROBLEM; + // } + + // (orientationInDegrees != 0.0) not spported +} + + +Point2 SpaceView::fwd(const PointLonLat&) const { + NOTIMP; +} + + +PointLonLat SpaceView::inv(const Point2&) const { + NOTIMP; +} + + +void SpaceView::fill_spec(spec::Custom& custom) const { + ProjectionOnFigure::fill_spec(custom); + + custom.set("projection", "geos"); //? + NOTIMP; +} + + +namespace { + + +void init() { + double lonOfSubSatellitePointInDegrees = 0.; + double nrInRadiusOfEarth = 1.; + double xpInGridLengths = 0.; + double ypInGridLengths = 0.; + long Xo = 0; + long Yo = 0; + + double earthMajorAxis = 1. / 1000.; // [km] + double earthMinorAxis = 1. / 1000.; + + double angular_size = 2.0 * asin(1.0 / nrInRadiusOfEarth); + double height = nrInRadiusOfEarth * earthMajorAxis; + + double lop = lonOfSubSatellitePointInDegrees; + + double dx = 1.; + double dy = 1.; + + auto x0 = static_cast(Xo); + auto y0 = static_cast(Yo); + + double xp = xpInGridLengths - x0; + double yp = ypInGridLengths - y0; + + double rx = angular_size / dx; + double ry = (earthMinorAxis / earthMajorAxis) * angular_size / dy; + + Point2 p; + Point2 q{(p.X - xp) * rx, (p.Y - yp) * ry}; + + double factor_1 = height * height - earthMajorAxis * earthMajorAxis; + double factor_2 = (earthMajorAxis / earthMinorAxis) * (earthMajorAxis / earthMinorAxis); + double factor_3 = (1 + (factor_2 - 1.0) * sin(q.Y) * sin(q.Y)); + + double Sd = height * cos(q.X) * cos(q.Y); + Sd = sqrt(Sd * Sd - factor_3 * factor_1); + if (Sd <= 0.0) { + Point q; + } + else { + double Sn = (height * cos(q.X) * cos(q.Y) - Sd) / factor_3; + double S1 = height - Sn * cos(q.X) * cos(q.Y); + double S2 = Sn * sin(q.X) * cos(q.Y); + double S3 = Sn * sin(q.Y); + double Sxy = sqrt(S1 * S1 + S2 * S2); + + auto lonr = atan(S2 / S1) + lop; + auto latr = atan(factor_2 * S3 / Sxy); + PointLonLat::make(util::RADIAN_TO_DEGREE * lonr, util::RADIAN_TO_DEGREE * latr); + } +} + + +} // namespace + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/SpaceView.h b/src/eckit/geo/projection/SpaceView.h new file mode 100644 index 000000000..1915a4c37 --- /dev/null +++ b/src/eckit/geo/projection/SpaceView.h @@ -0,0 +1,85 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/projection/ProjectionOnFigure.h" + + +namespace eckit::geo::projection { + + +/** + * @brief SpaceView projection + * @ref LRIT/HRIT Global Specification (CGMS 03, Issue 2.6, 12.08.1999) + */ +class SpaceView : public ProjectionOnFigure { +public: + // -- Types + // None + + // -- Exceptions + // None + + // -- Constructors + + explicit SpaceView(const Spec&); + + // -- Destructor + // None + + // -- Convertors + // None + + // -- Operators + // None + + // -- Methods + + Point2 fwd(const PointLonLat& p) const; + PointLonLat inv(const Point2& q) const; + + // -- Overridden methods + + inline Point fwd(const Point& p) const override { return fwd(std::get(p)); } + inline Point inv(const Point& q) const override { return inv(std::get(q)); } + + // -- Class members + // None + + // -- Class methods + // None + +protected: + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; + +private: + // -- Members + // None + + // -- Methods + // None + + // -- Class members + // None + + // -- Class methods + // None + + // -- Friends + // None +}; + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/Stretch.cc b/src/eckit/geo/projection/Stretch.cc new file mode 100644 index 000000000..35a1977e6 --- /dev/null +++ b/src/eckit/geo/projection/Stretch.cc @@ -0,0 +1,53 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/projection/Stretch.h" + +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/geo/util.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::projection { + + +static ProjectionBuilder PROJECTION("stretch"); + + +Stretch::Stretch(double c) : c_(c) { + if (types::is_approximately_equal(c_, 0.)) { + throw ProjectionProblem("Stretch: stretching_factor != 0", Here()); + } + ASSERT(c_ != 0.); +} + + +Stretch::Stretch(const Spec& spec) : Stretch(spec.get_double("stretching_factor")) {} + + +double Stretch::stretch(double a, double c) { + auto ar = util::DEGREE_TO_RADIAN * a; + ar = std::asin(std::cos(2. * std::atan(c * std::tan(std::acos(std::sin(ar)) * 0.5)))); + return util::RADIAN_TO_DEGREE * ar; +} + + +void Stretch::fill_spec(spec::Custom& spec) const { + spec.set("projection", "stretch"); + spec.set("stretching_factor", c_); +} + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/Stretch.h b/src/eckit/geo/projection/Stretch.h new file mode 100644 index 000000000..0a50139f3 --- /dev/null +++ b/src/eckit/geo/projection/Stretch.h @@ -0,0 +1,54 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/Projection.h" + + +namespace eckit::geo::projection { + + +class Stretch : public Projection { +public: + // -- Constructors + + explicit Stretch(double c); + explicit Stretch(const Spec&); + + // -- Methods + + inline PointLonLat fwd(const PointLonLat& p) const { return PointLonLat::make(p.lon, stretch(p.lat, 1. / c_)); } + inline PointLonLat inv(const PointLonLat& p) const { return PointLonLat::make(p.lon, stretch(p.lat, c_)); } + + // -- Overridden methods + + inline Point fwd(const Point& p) const override { return fwd(std::get(p)); } + inline Point inv(const Point& q) const override { return inv(std::get(q)); } + +protected: + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; + +private: + // -- Members + + double c_; + + // -- Methods + + static double stretch(double a, double c); +}; + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/XYToLonLat.cc b/src/eckit/geo/projection/XYToLonLat.cc new file mode 100644 index 000000000..e12d6f623 --- /dev/null +++ b/src/eckit/geo/projection/XYToLonLat.cc @@ -0,0 +1,30 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/projection/XYToLonLat.h" + +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo::projection { + + +static ProjectionBuilder PROJECTION1("xy_to_ll"); +static ProjectionBuilder PROJECTION2("plate-carree"); + + +void XYToLonLat::fill_spec(spec::Custom& custom) const { + custom.set("projection", "ll_to_xy"); +} + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/projection/XYToLonLat.h b/src/eckit/geo/projection/XYToLonLat.h new file mode 100644 index 000000000..cd9e5f462 --- /dev/null +++ b/src/eckit/geo/projection/XYToLonLat.h @@ -0,0 +1,48 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/Projection.h" + + +namespace eckit::geo::projection { + + +class XYToLonLat : public Projection { +public: + // -- Constructors + + explicit XYToLonLat() = default; + explicit XYToLonLat(const Spec&) {} + + // -- Methods + + inline PointLonLat fwd(const Point2& p) const { return {p.X, p.Y}; } + inline Point2 inv(const PointLonLat& q) const { return {q.lon, q.lat}; } + + // -- Overridden methods + + inline Point fwd(const Point& p) const override { return fwd(std::get(p)); } + inline Point inv(const Point& q) const override { return inv(std::get(q)); } + +protected: + // -- Overridden methods + + void fill_spec(spec::Custom&) const override; +}; + + +using PlateCaree = XYToLonLat; + + +} // namespace eckit::geo::projection diff --git a/src/eckit/geo/range/GaussianLatitude.cc b/src/eckit/geo/range/GaussianLatitude.cc new file mode 100644 index 000000000..46e46202d --- /dev/null +++ b/src/eckit/geo/range/GaussianLatitude.cc @@ -0,0 +1,75 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/range/GaussianLatitude.h" + +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/util.h" +#include "eckit/geo/util/mutex.h" +#include "eckit/types/FloatCompare.h" +#include "eckit/types/Fraction.h" + + +namespace eckit::geo::range { + + +static util::recursive_mutex MUTEX; + + +GaussianLatitude::GaussianLatitude(size_t N, bool increasing, double eps) : + Range(2 * N, increasing ? -90. : 90., increasing ? 90. : -90., eps), N_(N) {} + + +Range* GaussianLatitude::make_range_flipped() const { + std::vector flipped(size()); + const auto& v = values(); + std::reverse_copy(v.begin(), v.end(), flipped.begin()); + + return new GaussianLatitude(N_, std::move(flipped), eps()); +} + + +Range* GaussianLatitude::make_range_cropped(double crop_a, double crop_b) const { + ASSERT((a() < b() && crop_a <= crop_b) || (a() > b() && crop_a >= crop_b) + || (types::is_approximately_equal(a(), b(), eps()) && types::is_approximately_equal(crop_a, crop_b, eps()))); + + auto v = values(); + + if ((a() < b()) && (a() < crop_a || crop_b < b())) { + auto [from, to] = util::monotonic_crop(v, crop_a, crop_b, eps()); + v.erase(v.begin() + to, v.end()); + v.erase(v.begin(), v.begin() + from); + } + else if ((b() < a()) && (b() < crop_b || crop_a < a())) { + auto [from, to] = util::monotonic_crop(v, crop_b, crop_a, eps()); + v.erase(v.begin() + to, v.end()); + v.erase(v.begin(), v.begin() + from); + } + + return new GaussianLatitude(N_, std::move(v), eps()); +} + + +Fraction GaussianLatitude::increment() const { + NOTIMP; +} + + +const std::vector& GaussianLatitude::values() const { + util::lock_guard lock(MUTEX); + return values_.empty() ? util::gaussian_latitudes(N_, a() < b()) : values_; +} + + +} // namespace eckit::geo::range diff --git a/src/eckit/geo/range/GaussianLatitude.h b/src/eckit/geo/range/GaussianLatitude.h new file mode 100644 index 000000000..06d82f716 --- /dev/null +++ b/src/eckit/geo/range/GaussianLatitude.h @@ -0,0 +1,52 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/Range.h" + + +namespace eckit::geo::range { + + +class GaussianLatitude final : public Range { +public: + // -- Constructors + + explicit GaussianLatitude(size_t N, bool increasing, double eps = 0.); + + // -- Methods + + size_t N() const { return N_; } + + // -- Overridden methods + + [[nodiscard]] Range* make_range_flipped() const override; + [[nodiscard]] Range* make_range_cropped(double crop_a, double crop_b) const override; + + Fraction increment() const override; + const std::vector& values() const override; + +private: + // -- Constructors + + GaussianLatitude(size_t N, std::vector&& values, double _eps) : + Range(values.size(), values.front(), values.back(), _eps), N_(N), values_(values) {} + + // -- Members + + const size_t N_; + std::vector values_; +}; + + +} // namespace eckit::geo::range diff --git a/src/eckit/geo/range/Regular.cc b/src/eckit/geo/range/Regular.cc new file mode 100644 index 000000000..bd909ac07 --- /dev/null +++ b/src/eckit/geo/range/Regular.cc @@ -0,0 +1,78 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/range/Regular.h" + +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/util.h" +#include "eckit/geo/util/mutex.h" +#include "eckit/types/FloatCompare.h" +#include "eckit/types/Fraction.h" + + +namespace eckit::geo::range { + + +Regular::Regular(double _inc, double _a, double _b, double _ref, double eps) : Range(2, _a, _b, eps), periodic_(false) { + ASSERT(0. <= _inc); + + Fraction inc(_inc); + if (inc == 0 || types::is_approximately_equal(_a, _b)) { + b(_a); + resize(1); + return; + } + + bool up = _a < _b; + auto shift = (Fraction(_ref) / inc).decimalPart() * inc; + auto __a = shift + adjust(Fraction(_a) - shift, inc, up); + auto __b = shift + adjust(Fraction(_b) - shift, inc, !up); + auto n = static_cast((Fraction::abs(__b - __a) / inc).integralPart() + 1); + + a(__a); + b(__b); + resize(n); +} + + +Fraction Regular::increment() const { + ASSERT(1 < size()); + return Fraction(std::abs(b() - a()) / static_cast(periodic() ? size() : (size() - 1))); +} + + +const std::vector& Regular::values() const { + static util::recursive_mutex MUTEX; + util::lock_guard lock(MUTEX); + + if (values_.empty()) { + const_cast&>(values_) = util::linspace(a(), b(), size(), !periodic_); + ASSERT(!values_.empty()); + } + + return values_; +} + + +Fraction Regular::adjust(const Fraction& target, const Fraction& inc, bool up) { + ASSERT(inc > 0); + + auto r = target / inc; + auto n = r.integralPart() + ((r.integer() || (r > 0) != up) ? 0 : up ? 1 : -1); + + return n * inc; +} + + +} // namespace eckit::geo::range diff --git a/src/eckit/geo/range/Regular.h b/src/eckit/geo/range/Regular.h new file mode 100644 index 000000000..a43532ba4 --- /dev/null +++ b/src/eckit/geo/range/Regular.h @@ -0,0 +1,65 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/Range.h" + + +namespace eckit::geo::range { + + +class Regular : public Range { +public: + // -- Methods + + Fraction increment() const override; + + // -- Overridden methods + + const std::vector& values() const override; + +protected: + // -- Constructors + + /** + * @brief Regular + * @param inc regular increment + * @param a range start + * @param b range end + * @param ref User-defined coordinate reachable with (multiples of) integer increment; Can be defined out of the [a, + * b] range. Support both "shifted" and "non-shifted" ranges, for the same definition of [a, b] range and increment + * @param eps tolerace to check range start/end against + */ + Regular(double inc, double a, double b, double ref, double eps); + + Regular(size_t n, double a, double b, bool periodic, double eps) : Range(n, a, b, eps), periodic_(periodic) {} + + Regular(size_t n, double a, double b, std::vector&& values, bool periodic, double eps) : + Range(n, a, b, eps), values_(values), periodic_(periodic) {} + + // -- Methods + + static Fraction adjust(const Fraction& target, const Fraction& inc, bool up); + + void setPeriodic(bool p) { periodic_ = p; } + bool getPeriodic() const { return periodic_; } + +private: + // -- Members + + std::vector values_; + bool periodic_; +}; + + +} // namespace eckit::geo::range diff --git a/src/eckit/geo/range/RegularCartesian.cc b/src/eckit/geo/range/RegularCartesian.cc new file mode 100644 index 000000000..90cb59fbe --- /dev/null +++ b/src/eckit/geo/range/RegularCartesian.cc @@ -0,0 +1,74 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/range/RegularCartesian.h" + +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/types/FloatCompare.h" +#include "eckit/types/Fraction.h" + + +namespace eckit::geo::range { + + +static constexpr auto DB = 1e-12; + + +static Fraction regular_adjust(const Fraction& target, const Fraction& inc, bool up) { + ASSERT(inc > 0); + + auto r = target / inc; + auto n = r.integralPart() + ((r.integer() || (r > 0) != up) ? 0 : up ? 1 : -1); + + return n * inc; +}; + + +Range* RegularCartesian::make_range_cropped(double crop_a, double crop_b) const { + ASSERT((a() < b() && crop_a <= crop_b) || (a() > b() && crop_a >= crop_b) + || (types::is_approximately_equal(a(), b(), eps()) && types::is_approximately_equal(crop_a, crop_b, eps()))); + + if (types::is_approximately_equal(crop_a, crop_b, eps())) { + NOTIMP; // FIXME + } + else if (a() < b()) { + ASSERT(a() <= crop_a && crop_b <= b()); // FIXME do better + + auto inc(increment()); + auto d = (a() / inc).decimalPart() * inc; + auto _a = regular_adjust(crop_a - d, inc, true) + d; + auto _b = regular_adjust(crop_b - d, inc, false) + d; + + auto nf = (_b - _a) / inc; + ASSERT(nf.integer()); + + auto n = static_cast(nf.integralPart() + 1); + ASSERT(0 < n && n <= size()); + + return new RegularCartesian(n, _a, _b, eps()); + } + else { + NOTIMP; // FIXME + } + + NOTIMP; +} + + +Range* RegularCartesian::make_range_flipped() const { + return new RegularCartesian(size(), b(), a(), eps()); +} + + +} // namespace eckit::geo::range diff --git a/src/eckit/geo/range/RegularCartesian.h b/src/eckit/geo/range/RegularCartesian.h new file mode 100644 index 000000000..3c7b6c981 --- /dev/null +++ b/src/eckit/geo/range/RegularCartesian.h @@ -0,0 +1,36 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/range/Regular.h" + + +namespace eckit::geo::range { + + +class RegularCartesian final : public Regular { +public: + // -- Constructors + + using Regular::Regular; + + RegularCartesian(size_t n, double a, double b, double eps = 0.) : Regular(n, a, b, false, eps) {} + + // -- Overridden methods + + [[nodiscard]] Range* make_range_cropped(double crop_a, double crop_b) const override; + [[nodiscard]] Range* make_range_flipped() const override; +}; + + +} // namespace eckit::geo::range diff --git a/src/eckit/geo/range/RegularLatitude.cc b/src/eckit/geo/range/RegularLatitude.cc new file mode 100644 index 000000000..6e467ceb5 --- /dev/null +++ b/src/eckit/geo/range/RegularLatitude.cc @@ -0,0 +1,64 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/range/RegularLatitude.h" + +#include "eckit/exception/Exceptions.h" +#include "eckit/types/FloatCompare.h" +#include "eckit/types/Fraction.h" + + +namespace eckit::geo::range { + + +RegularLatitude::RegularLatitude(double _inc, double _a, double _b, double _ref, double _eps) : + Regular(_inc, _a, _b, _ref, _eps) {} + + +RegularLatitude::RegularLatitude(size_t n, double _a, double _b, double _eps) : Regular(n, _a, _b, false, _eps) { + ASSERT(-90. <= a() && a() <= 90.); + ASSERT(-90. <= b() && b() <= 90.); +} + + +Range* RegularLatitude::make_range_cropped(double crop_a, double crop_b) const { + ASSERT((a() < b() && crop_a <= crop_b) || (a() > b() && crop_a >= crop_b) + || (types::is_approximately_equal(a(), b(), eps()) && types::is_approximately_equal(crop_a, crop_b, eps()))); + + if (types::is_approximately_equal(crop_a, crop_b, eps())) { + NOTIMP; // FIXME + } + else if (a() < b()) { + ASSERT(a() <= crop_a && crop_b <= b()); // FIXME do better + + const auto inc(increment()); + const auto d = (a() / inc).decimalPart() * inc; + const auto _a = adjust(crop_a - d, inc, true) + d; + const auto _b = adjust(crop_b - d, inc, false) + d; + + const auto nf = (_b - _a) / inc; + ASSERT(nf.integer()); + + const auto n = static_cast(nf.integralPart() + 1); + ASSERT(0 < n && n <= size()); + + return new RegularLatitude(n, _a, _b, eps()); + } + else { + NOTIMP; // FIXME + } + + NOTIMP; +} + + +} // namespace eckit::geo::range diff --git a/src/eckit/geo/range/RegularLatitude.h b/src/eckit/geo/range/RegularLatitude.h new file mode 100644 index 000000000..b6c5bf93b --- /dev/null +++ b/src/eckit/geo/range/RegularLatitude.h @@ -0,0 +1,35 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include "eckit/geo/range/Regular.h" + + +namespace eckit::geo::range { + + +class RegularLatitude final : public Regular { +public: + // -- Constructors + + explicit RegularLatitude(double inc, double a, double b, double ref, double eps = 0.); + explicit RegularLatitude(size_t n, double a, double b, double eps = 0.); + + // -- Overridden methods + + [[nodiscard]] Range* make_range_cropped(double crop_a, double crop_b) const override; + [[nodiscard]] Range* make_range_flipped() const override { return new RegularLatitude(size(), b(), a(), eps()); } +}; + + +} // namespace eckit::geo::range diff --git a/src/eckit/geo/range/RegularLongitude.cc b/src/eckit/geo/range/RegularLongitude.cc new file mode 100644 index 000000000..df091ac5f --- /dev/null +++ b/src/eckit/geo/range/RegularLongitude.cc @@ -0,0 +1,99 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/range/RegularLongitude.h" + +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/PointLonLat.h" +#include "eckit/types/FloatCompare.h" +#include "eckit/types/Fraction.h" + + +namespace eckit::geo::range { + + +static const Fraction PERIOD(360, 1); + + +RegularLongitude::RegularLongitude(double _inc, double _a, double _b, double _ref, double _eps) : + Regular(_inc, _a, _b, _ref, _eps) { + ASSERT(!types::is_approximately_equal(_a, _b)); + ASSERT(_a < _b); // FIXME temporary + const Fraction inc(_inc); + + auto n = 1 + (std::min(Fraction(b() - a()), PERIOD) / inc).integralPart(); + setPeriodic(n * inc >= PERIOD); + + if (periodic()) { + b(a() + PERIOD); + resize((PERIOD / inc).integralPart()); + } + else { + b(Fraction(a()) + (n - 1) * inc); + resize(n); + } +} + + +RegularLongitude::RegularLongitude(size_t n, double _a, double _b, double _eps) : + Regular(n, _a, _b, types::is_approximately_lesser_or_equal(PERIOD, std::abs(_b - _a)), _eps) {} + + +Range* RegularLongitude::make_range_cropped(double crop_a, double crop_b) const { + ASSERT((a() < b() && crop_a <= crop_b) || (a() > b() && crop_a >= crop_b)); + + if (a() < b()) { + const auto inc(increment()); + + if (periodic()) { + return new RegularLongitude(inc, crop_a, crop_b, a(), eps()); + } + + RegularLongitude crop(inc, crop_a, crop_b, a(), eps()); + if (crop.periodic()) { + auto _a = std::max(a(), PointLonLat::normalise_angle_to_minimum(crop_a, crop.a())); + auto _b = PointLonLat::normalise_angle_to_minimum(crop_b, _a); + return new RegularLongitude(inc, _a, _b, a(), eps()); + } + + ASSERT(a() <= crop_a && crop_b <= b()); // FIXME do better + + auto d = (a() / inc).decimalPart() * inc; + auto _a = adjust(crop_a - d, inc, true) + d; + auto _b = adjust(crop_b - d, inc, false) + d; + + auto nf = (_b - _a) / inc; + ASSERT(nf.integer()); + + auto n = static_cast(nf.integralPart() + (nf * inc >= PERIOD ? 0 : 1)); + ASSERT(0 < n && n <= size()); + + return new RegularLongitude(n, _a, _b, eps()); + } + + NOTIMP; +} + + +Range* RegularLongitude::make_range_flipped() const { + std::vector flipped(size()); + const auto& v = values(); + std::reverse_copy(v.begin(), v.end(), flipped.begin()); + + return new RegularLongitude(size(), b(), a(), eps()); +} + + +} // namespace eckit::geo::range diff --git a/src/eckit/geo/range/RegularLongitude.h b/src/eckit/geo/range/RegularLongitude.h new file mode 100644 index 000000000..916b8f12d --- /dev/null +++ b/src/eckit/geo/range/RegularLongitude.h @@ -0,0 +1,47 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + +#include "eckit/geo/range/Regular.h" + + +namespace eckit::geo::range { + + +class RegularLongitude final : public Regular { +public: + // -- Constructors + + explicit RegularLongitude(double inc, double a, double b, double ref, double eps = 0.); + explicit RegularLongitude(double inc, double a, double b, double eps = 0.) : RegularLongitude(inc, a, b, a, eps) {} + + explicit RegularLongitude(size_t n, double a, double b, double eps = 0.); + + // -- Overridden methods + + [[nodiscard]] Range* make_range_cropped(double crop_a, double crop_b) const override; + [[nodiscard]] Range* make_range_flipped() const override; + + bool periodic() const override { return getPeriodic(); } + +private: + // -- Constructors + + RegularLongitude(size_t n, double a, double b, std::vector&& values, bool periodic, double eps) : + Regular(n, a, b, std::move(values), periodic, eps) {} +}; + + +} // namespace eckit::geo::range diff --git a/src/eckit/geo/spec/Custom.cc b/src/eckit/geo/spec/Custom.cc new file mode 100644 index 000000000..be6ea6f3e --- /dev/null +++ b/src/eckit/geo/spec/Custom.cc @@ -0,0 +1,513 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/spec/Custom.h" + +#include +#include +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/util.h" +#include "eckit/log/JSON.h" +#include "eckit/value/Content.h" // for ValueList, ValueMap +#include "eckit/value/Value.h" + + +namespace eckit::geo::spec { + + +namespace { + + +constexpr int STREAM_PRECISION = 15; + + +template +bool get_t_s(const From& from, To& to) { + to = static_cast(from); + return true; +} + + +template +bool get_t_s(const From& from, std::string& to) { + to = std::to_string(from); + return true; +} + + +template +bool get_t_s(const From& from, From& to) { + to = from; + return true; +} + + +template +bool get_t_v(const std::vector& from, std::vector& to) { + to.clear(); + for (const auto& f : from) { + to.emplace_back(static_cast(f)); + } + return true; +} + + +template +bool get_t_v(const std::vector& from, std::vector& to) { + to.clear(); + for (const auto& f : from) { + to.emplace_back(std::to_string(f)); + } + return true; +} + + +template +bool get_t_s_integral(const Custom::container_type& map, const std::string& name, T& value) { + if (auto it = map.find(name); it != map.cend()) { + const auto& v = it->second; + return std::holds_alternative(v) ? get_t_s(std::get(v), value) + : std::holds_alternative(v) ? get_t_s(std::get(v), value) + : std::holds_alternative(v) ? get_t_s(std::get(v), value) + : std::holds_alternative(v) ? get_t_s(std::get(v), value) + : false; + } + return false; +} + + +template +bool get_t_s_real(const Custom::container_type& map, const std::string& name, T& value) { + if (get_t_s_integral(map, name, value)) { + return true; + } + + if (auto it = map.find(name); it != map.cend()) { + const auto& v = it->second; + return std::holds_alternative(v) ? get_t_s(std::get(v), value) + : std::holds_alternative(v) ? get_t_s(std::get(v), value) + : false; + } + return false; +} + + +template +bool get_t_v_integral(const Custom::container_type& map, const std::string& name, T& value) { + if (auto it = map.find(name); it != map.cend()) { + const auto& v = it->second; + return std::holds_alternative>(v) ? get_t_v(std::get>(v), value) + : std::holds_alternative>(v) ? get_t_v(std::get>(v), value) + : std::holds_alternative>(v) ? get_t_v(std::get>(v), value) + : std::holds_alternative>(v) ? get_t_v(std::get>(v), value) + : false; + } + return false; +} + + +template +bool get_t_v_real(const Custom::container_type& map, const std::string& name, T& value) { + if (get_t_v_integral(map, name, value)) { + return true; + } + + if (auto it = map.find(name); it != map.cend()) { + const auto& v = it->second; + return std::holds_alternative>(v) ? get_t_v(std::get>(v), value) + : std::holds_alternative>(v) ? get_t_v(std::get>(v), value) + : false; + } + return false; +} + + +template +Custom::value_type from_value_t(const Value& value) { + T to; + fromValue(to, value); + return {to}; +} + + +void sanitise(Custom::container_type& container) { + std::for_each(container.begin(), container.end(), [](auto& p) { + if (auto& value = p.second; std::holds_alternative(value)) { + value = std::string{std::get(value)}; + } + else if (std::holds_alternative(value)) { + ASSERT(std::get(value)); + } + }); +} + + +} // namespace + + +Custom::key_type::key_type(const std::string& s) : std::string{s} { + std::transform(begin(), end(), begin(), [](unsigned char c) -> unsigned char { return std::tolower(c); }); +} + + +Custom::Custom(std::initializer_list init) : map_(init) { + sanitise(map_); +} + + +Custom::Custom(const Custom::container_type& map) : map_(map) { + sanitise(map_); +} + + +Custom::Custom(Custom::container_type&& map) : map_(map) { + sanitise(map_); +} + + +Custom* Custom::make_from_value(const Value& value) { + ASSERT(value.isMap()); + + auto scalar = [](const Value& value) -> value_type { + return value.isNumber() ? value_type(static_cast(value)) + : value.isDouble() ? value_type(static_cast(value)) + : value.isString() ? static_cast(value) + : throw BadValue(value, Here()); + }; + + auto vector = [](const Value& value) -> value_type { + const auto list(value.as()); + ASSERT(!list.empty()); + + return list.front().isNumber() ? value_type(std::vector(list.begin(), list.end())) + : list.front().isDouble() ? value_type(std::vector(list.begin(), list.end())) + : list.front().isString() ? std::vector(list.begin(), list.end()) + : throw BadValue(value, Here()); + }; + + Custom::container_type container; + for (const auto& [key, value] : static_cast(value)) { + const std::string name = key; + + container[name] = value.isMap() ? custom_ptr(Custom::make_from_value(value)) + : value.isList() ? vector(value) + : scalar(value); + } + + return new Custom(std::move(container)); +} + + +bool Custom::operator==(const Custom& other) const { + auto custom_value_equal + = [](const Custom& ca, const Custom& cb, const Custom::key_type& name, const auto& type_instance) -> bool { + auto a = type_instance; + auto b = type_instance; + return ca.get(name, a) && cb.get(name, b) && a == b; + }; + + // check every local key exists in other and is convertible to an equal value + return std::all_of(map_.begin(), map_.end(), [&](const auto& _a) { + const auto& name = _a.first; + auto _b = other.map_.find(name); + return _b != other.map_.end() + && (custom_value_equal(*this, other, name, long{}) + || custom_value_equal(*this, other, name, std::vector{}) + || custom_value_equal(*this, other, name, double{}) + || custom_value_equal(*this, other, name, std::vector{}) + || custom_value_equal(*this, other, name, std::string{}) + || custom_value_equal(*this, other, name, std::vector{})); + }); +} + + +void Custom::set(const std::string& name, const std::string& value) { + map_[name] = value; +} + + +void Custom::set(const std::string& name, bool value) { + map_[name] = value; +} + + +void Custom::set(const std::string& name, int value) { + map_[name] = value; +} + + +void Custom::set(const std::string& name, long value) { + map_[name] = value; +} + + +void Custom::set(const std::string& name, long long value) { + map_[name] = value; +} + + +void Custom::set(const std::string& name, size_t value) { + map_[name] = value; +} + + +void Custom::set(const std::string& name, float value) { + map_[name] = value; +} + + +void Custom::set(const std::string& name, double value) { + map_[name] = value; +} + + +void Custom::set(const std::string& name, const std::vector& value) { + map_[name] = value; +} + + +void Custom::set(const std::string& name, const std::vector& value) { + map_[name] = value; +} + + +void Custom::set(const std::string& name, const std::vector& value) { + map_[name] = value; +} + + +void Custom::set(const std::string& name, const std::vector& value) { + map_[name] = value; +} + + +void Custom::set(const std::string& name, const std::vector& value) { + map_[name] = value; +} + + +void Custom::set(const std::string& name, const std::vector& value) { + map_[name] = value; +} + + +void Custom::set(const std::string& name, const std::vector& value) { + map_[name] = value; +} + + +void Custom::set(const std::string& key, const Value& value) { + using number_type = pl_type::value_type; + + auto list_of = [](const ValueList& list, auto pred) { return std::all_of(list.begin(), list.end(), pred); }; + + auto val = value.isList() && list_of(value, [](const Value& v) { return v.isDouble(); }) + ? from_value_t>(value) + : value.isList() && list_of(value, [](const Value& v) { return v.isNumber(); }) + ? from_value_t>(value) + : value.isList() ? from_value_t>(value) + : value.isDouble() ? from_value_t(value) + : value.isNumber() ? from_value_t(value) + : from_value_t(value); + + std::visit([&](const auto& val) { set(key, val); }, val); +} + + +void Custom::set(const std::string& name, Custom* value) { + ASSERT(value != nullptr); + map_[name] = custom_ptr(value); +} + + +bool Custom::has_custom(const std::string& name) const { + auto it = map_.find(name); + return it != map_.cend() && std::holds_alternative(it->second); +} + + +const Custom::custom_ptr& Custom::custom(const std::string& name) const { + if (auto it = map_.find(name); it != map_.cend()) { + if (std::holds_alternative(it->second)) { + const auto& value = std::get(it->second); + ASSERT(value); + + return value; + } + } + + throw SpecNotFound("Custom::get(" + name + ") -> custom_type& ", Here()); +} + + +void Custom::set(const std::string& name, const custom_ptr& value) { + ASSERT(value); + map_[name] = value; +} + + +bool Custom::has(const std::string& name) const { + return map_.find(name) != map_.cend(); +} + + +bool Custom::get(const std::string& name, std::string& value) const { + if (auto it = map_.find(name); it != map_.cend() /*&& )*/) { + if (std::holds_alternative(it->second)) { + value = std::get(it->second); + return true; + } + + return get_t_s_real(map_, name, value); + } + return false; +} + + +bool Custom::get(const std::string& name, bool& value) const { + if (auto it = map_.find(name); it != map_.cend()) { + if (std::holds_alternative(it->second)) { + value = std::get(it->second); + return true; + } + + if (int i = 0; get_t_s_integral(map_, name, i)) { + value = i != 0; + return true; + } + } + return false; +} + + +bool Custom::get(const std::string& name, int& value) const { + return get_t_s_integral(map_, name, value); +} + + +bool Custom::get(const std::string& name, long& value) const { + return get_t_s_integral(map_, name, value); +} + + +bool Custom::get(const std::string& name, long long& value) const { + return get_t_s_integral(map_, name, value); +} + + +bool Custom::get(const std::string& name, size_t& value) const { + return get_t_s_integral(map_, name, value); +} + + +bool Custom::get(const std::string& name, float& value) const { + return get_t_s_real(map_, name, value); +} + + +bool Custom::get(const std::string& name, double& value) const { + return get_t_s_real(map_, name, value); +} + + +bool Custom::get(const std::string& name, std::vector& value) const { + return get_t_v_integral(map_, name, value); +} + + +bool Custom::get(const std::string& name, std::vector& value) const { + return get_t_v_integral(map_, name, value); +} + + +bool Custom::get(const std::string& name, std::vector& value) const { + return get_t_v_integral(map_, name, value); +} + + +bool Custom::get(const std::string& name, std::vector& value) const { + return get_t_v_integral(map_, name, value); +} + + +bool Custom::get(const std::string& name, std::vector& value) const { + return get_t_v_real(map_, name, value); +} + + +bool Custom::get(const std::string& name, std::vector& value) const { + return get_t_v_real(map_, name, value); +} + + +bool Custom::get(const std::string& name, std::vector& value) const { + if (auto it = map_.find(name); it != map_.cend()) { + if (std::holds_alternative>(it->second)) { + value = std::get>(it->second); + return true; + } + } + return false; +} + + +void Custom::json(JSON& j) const { + j.startObject(); + j.precision(STREAM_PRECISION); + for (const auto& [key, value] : map_) { + j << key; + std::visit([&](const auto& arg) { j << arg; }, value); + } + j.endObject(); +} + + +JSON& operator<<(JSON& j, const Custom::custom_ptr& value) { + ASSERT(value); + j.startObject(); + for (const auto& [key, value] : value->container()) { + j << key; + std::visit([&](const auto& arg) { j << arg; }, value); + } + + j.endObject(); + return j; +} + + +template +struct is_vector : std::false_type {}; + + +template +struct is_vector> : std::true_type {}; + + +template +constexpr bool is_vector_v = is_vector::value; + + +std::string to_string(const Custom::value_type& value) { + return std::visit( + [&](const auto& arg) { + std::ostringstream str; + str.precision(STREAM_PRECISION); + str << arg; + return str.str(); + }, + value); +} + + +} // namespace eckit::geo::spec diff --git a/src/eckit/geo/spec/Custom.h b/src/eckit/geo/spec/Custom.h new file mode 100644 index 000000000..b2aca90a7 --- /dev/null +++ b/src/eckit/geo/spec/Custom.h @@ -0,0 +1,135 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include +#include +#include + +#include "eckit/geo/Spec.h" + + +namespace eckit { +class Value; +} + + +namespace eckit::geo::spec { + + +class Custom final : public Spec { +public: + // -- Types + + struct custom_ptr : std::shared_ptr { + using shared_ptr::shared_ptr; + }; + + struct key_type : std::string { + key_type(const std::string&); + key_type(const char* s) : key_type(std::string{s}) {} + }; + + using value_type = std::variant, + std::vector, std::vector, std::vector, std::vector, + std::vector, std::vector, custom_ptr, + const char* /* converted to std::string */>; + + using container_type = std::map; + + // -- Constructors + + Custom() = default; + Custom(std::initializer_list); + + explicit Custom(const container_type&); + explicit Custom(container_type&&); + + // -- Operators + + bool operator==(const Custom&) const; + bool operator!=(const Custom& other) const { return !operator==(other); } + + // -- Methods + + const container_type& container() const { return map_; } + bool empty() const { return map_.empty(); } + void clear() { map_.clear(); } + + void json(JSON&) const override; + + bool has_custom(const std::string& name) const; + const custom_ptr& custom(const std::string& name) const; + + void set(const std::string& name, const std::string&); + void set(const std::string& name, bool); + void set(const std::string& name, int); + void set(const std::string& name, long); + void set(const std::string& name, long long); + void set(const std::string& name, size_t); + void set(const std::string& name, float); + void set(const std::string& name, double); + void set(const std::string& name, const std::vector&); + void set(const std::string& name, const std::vector&); + void set(const std::string& name, const std::vector&); + void set(const std::string& name, const std::vector&); + void set(const std::string& name, const std::vector&); + void set(const std::string& name, const std::vector&); + void set(const std::string& name, const std::vector&); + + void set(const std::string& name, const char* value) { set(name, std::string{value}); } + void set(const std::string& name, const Value&); + void set(const std::string& name, Custom*); + + // -- Overridden methods + + bool has(const std::string& name) const override; + + bool get(const std::string& name, std::string&) const override; + bool get(const std::string& name, bool&) const override; + bool get(const std::string& name, int&) const override; + bool get(const std::string& name, long&) const override; + bool get(const std::string& name, long long&) const override; + bool get(const std::string& name, size_t&) const override; + bool get(const std::string& name, float&) const override; + bool get(const std::string& name, double&) const override; + bool get(const std::string& name, std::vector&) const override; + bool get(const std::string& name, std::vector&) const override; + bool get(const std::string& name, std::vector&) const override; + bool get(const std::string& name, std::vector&) const override; + bool get(const std::string& name, std::vector&) const override; + bool get(const std::string& name, std::vector&) const override; + bool get(const std::string& name, std::vector&) const override; + + // -- Class methods + + static Custom* make_from_value(const Value&); + +private: + // -- Members + + container_type map_; + + // -- Methods + + void set(const std::string& name, const custom_ptr&); +}; + + +JSON& operator<<(JSON&, const Custom::custom_ptr&); + + +std::string to_string(const Custom::value_type&); + + +} // namespace eckit::geo::spec diff --git a/src/eckit/geo/spec/Generator.h b/src/eckit/geo/spec/Generator.h new file mode 100644 index 000000000..2827ed336 --- /dev/null +++ b/src/eckit/geo/spec/Generator.h @@ -0,0 +1,386 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/util/mutex.h" + + +namespace eckit::geo { +class Spec; +namespace spec { +class Custom; +} +} // namespace eckit::geo + + +namespace eckit::geo::spec { + +//------------------------------------------------------------------------------------------------------ + +template +class GeneratorT { +public: + // -- Types + + using generator_t = C; + using key_t = std::string; + using storage_t = std::map; + + // -- Constructors + + GeneratorT(const GeneratorT&) = delete; + GeneratorT(GeneratorT&&) = delete; + + // -- Operators + + void operator=(const GeneratorT&) = delete; + void operator=(GeneratorT&&) = delete; + + // -- Methods + + static GeneratorT& instance(); + + bool exists(const key_t&) const; + bool matches(const std::string&) const; + + void regist(const key_t&, generator_t*); + void unregist(const key_t&); + + const generator_t& get(const key_t&) const; + const generator_t& match(const std::string&) const; + + bool match(const Custom& spec, std::string& name) const { + auto end = store_.cend(); + auto i = end; + for (auto j = store_.cbegin(); j != end; ++j) { + if (!(j->first.empty()) && j->second->match(spec)) { + if (i != end) { + throw SeriousBug("Generator matches names '" + i->first + "' and '" + j->first + "'", Here()); + } + i = j; + } + } + + if (i != end) { + name = i->first; + ASSERT(!name.empty()); + return true; + } + + return false; + } + +private: + // -- Constructors + + GeneratorT() = default; + + // -- Destructor + + ~GeneratorT() = default; + + // -- Members + + mutable Mutex mutex_; + storage_t store_; + + // -- Methods + + void print(std::ostream&) const; + + // -- Friends + + friend std::ostream& operator<<(std::ostream& os, const GeneratorT& o) { + o.print(os); + return os; + } +}; + +//------------------------------------------------------------------------------------------------------ + +static util::recursive_mutex MUTEX; + +class lock_type { + util::lock_guard lock_guard_{MUTEX}; +}; + +//------------------------------------------------------------------------------------------------------ + +template +GeneratorT& GeneratorT::instance() { + static GeneratorT obj; + return obj; +} + +template +bool GeneratorT::exists(const key_t& k) const { + lock_type lock; + return store_.find(k) != store_.end(); +} + +template +bool GeneratorT::matches(const std::string& k) const { + lock_type lock; + return std::any_of(store_.begin(), store_.end(), + [&](const auto& p) -> bool { return std::regex_match(k, std::regex(p.first)); }); +} + +template +void GeneratorT::regist(const key_t& k, generator_t* c) { + lock_type lock; + if (exists(k)) { + throw BadParameter("Generator has already a builder for " + k, Here()); + } + ASSERT(c != nullptr); + store_[k] = c; +} + +template +void GeneratorT::unregist(const key_t& k) { + lock_type lock; + if (auto it = store_.find(k); it != store_.end()) { + store_.erase(it); + return; + } + throw BadParameter("Generator unknown: '" + k + "'", Here()); +} + +template +const typename GeneratorT::generator_t& GeneratorT::get(const key_t& k) const { + lock_type lock; + if (auto it = store_.find(k); it != store_.end()) { + return *(it->second); + } + throw BadParameter("Generator unknown: '" + k + "'", Here()); +} + +template +const typename GeneratorT::generator_t& GeneratorT::match(const std::string& k) const { + lock_type lock; + + auto end = store_.cend(); + auto i = end; + for (auto j = store_.cbegin(); j != end; ++j) { + if (std::regex_match(k, std::regex(j->first))) { + if (i != end) { + throw SeriousBug("Generator name '" + k + "' matches '" + i->first + "' and '" + j->first + "'", + Here()); + } + i = j; + } + } + + if (i != end) { + return *(i->second); + } + + throw BadParameter("Generator unknown: '" + k + "'", Here()); +} + +template +void GeneratorT::print(std::ostream& os) const { + lock_type lock; + os << "Generator" << std::endl; + + int key_width = 0; + for (const auto& i : store_) { + key_width = std::max(static_cast(i.first.size()), key_width); + } + + for (const auto& i : store_) { + os << " " << std::setw(key_width) << std::left << i.first << " -- " << i.second << std::endl; + } +} + +//------------------------------------------------------------------------------------------------------ + +class SpecGenerator { +public: + // -- Types + + using key_t = std::string; + + static constexpr const char* uid_pattern = "[0-9a-fA-F]{32}"; + + // -- Constructors + + SpecGenerator() = default; + SpecGenerator(const SpecGenerator&) = delete; + SpecGenerator(SpecGenerator&&) = delete; + + // -- Destructor + + virtual ~SpecGenerator() = default; + + // -- Operators + + void operator=(const SpecGenerator&) = delete; + void operator=(SpecGenerator&&) = delete; + + // -- Methods + + virtual bool match(const spec::Custom&) const { return false; } +}; + +//------------------------------------------------------------------------------------------------------ + +class SpecGeneratorT0 : public SpecGenerator { +public: + // -- Methods + + [[nodiscard]] virtual Spec* spec() const = 0; +}; + +//------------------------------------------------------------------------------------------------------ + +template +class SpecGeneratorT1 : public SpecGenerator { +public: + // -- Types + + using arg1_t = ARG1; + + // -- Methods + + [[nodiscard]] virtual Spec* spec(arg1_t) const = 0; +}; + +//------------------------------------------------------------------------------------------------------ + +template +class SpecGeneratorT2 : public SpecGenerator { +public: + // -- Types + + using arg1_t = ARG1; + using arg2_t = ARG2; + + // -- Methods + + [[nodiscard]] virtual Spec* spec(arg1_t, arg2_t) const = 0; +}; + +//------------------------------------------------------------------------------------------------------ + +template +class ConcreteSpecGeneratorT0 final : public SpecGeneratorT0 { +public: + // -- Constructors + + explicit ConcreteSpecGeneratorT0(const SpecGeneratorT0::key_t& k) : key_(k) { + GeneratorT::instance().regist(key_, this); + } + + ConcreteSpecGeneratorT0(const ConcreteSpecGeneratorT0&) = delete; + ConcreteSpecGeneratorT0(ConcreteSpecGeneratorT0&&) = delete; + + // -- Destructor + + ~ConcreteSpecGeneratorT0() override { GeneratorT::instance().unregist(key_); } + + // -- Operators + + void operator=(const ConcreteSpecGeneratorT0&) = delete; + void operator=(ConcreteSpecGeneratorT0&&) = delete; + + // -- Overridden methods + + [[nodiscard]] Spec* spec() const override { return T::spec(); } + +private: + // -- Members + + SpecGeneratorT0::key_t key_; +}; + +//------------------------------------------------------------------------------------------------------ + +template +class ConcreteSpecGeneratorT1 final : public SpecGeneratorT1 { +public: + // -- Constructors + + explicit ConcreteSpecGeneratorT1(const typename SpecGeneratorT1::key_t& k) : key_(k) { + GeneratorT>::instance().regist(key_, this); + } + + ConcreteSpecGeneratorT1(const ConcreteSpecGeneratorT1&) = delete; + ConcreteSpecGeneratorT1(ConcreteSpecGeneratorT1&&) = delete; + + // -- Destructor + + ~ConcreteSpecGeneratorT1() override { GeneratorT>::instance().unregist(key_); } + + // -- Operators + + void operator=(const ConcreteSpecGeneratorT1&) = delete; + void operator=(ConcreteSpecGeneratorT1&&) = delete; + + // -- Overridden methods + + [[nodiscard]] Spec* spec(typename SpecGeneratorT1::arg1_t p1) const override { return T::spec(p1); } + +private: + // -- Members + + typename SpecGeneratorT1::key_t key_; +}; + +//------------------------------------------------------------------------------------------------------ + +template +class ConcreteSpecGeneratorT2 final : public SpecGeneratorT2 { +public: + // -- Constructors + + explicit ConcreteSpecGeneratorT2(const typename SpecGeneratorT2::key_t& k) : key_(k) { + GeneratorT>::instance().regist(key_, this); + } + + ConcreteSpecGeneratorT2(const ConcreteSpecGeneratorT2&) = delete; + ConcreteSpecGeneratorT2(ConcreteSpecGeneratorT2&&) = delete; + + // -- Destructor + + ~ConcreteSpecGeneratorT2() override { GeneratorT>::instance().unregist(key_); } + + // -- Operators + + void operator=(const ConcreteSpecGeneratorT2&) = delete; + void operator=(ConcreteSpecGeneratorT2&&) = delete; + + // -- Overridden methods + + [[nodiscard]] Spec* spec(typename SpecGeneratorT1::arg1_t p1, + typename SpecGeneratorT1::arg2_t p2) const override { + return T::spec(p1, p2); + } + +private: + // -- Members + + typename SpecGeneratorT2::key_t key_; +}; + +//------------------------------------------------------------------------------------------------------ + +} // namespace eckit::geo::spec diff --git a/src/eckit/geo/spec/Layered.cc b/src/eckit/geo/spec/Layered.cc new file mode 100644 index 000000000..c31b05c5b --- /dev/null +++ b/src/eckit/geo/spec/Layered.cc @@ -0,0 +1,90 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + +#include "eckit/geo/spec/Layered.h" + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/log/JSON.h" +#include "eckit/value/Value.h" + + +namespace eckit::geo::spec { + + +static const Custom EMPTY; + + +Layered::Layered() : Layered(EMPTY) {} + + +Layered::Layered(const Spec& spec) : spec_(spec) {} + + +void Layered::hide(const std::string& name) { + hide_.insert(name); +} + + +void Layered::unhide(const std::string& name) { + hide_.erase(name); +} + + +void Layered::push_back(Spec* spec) { + ASSERT(spec != nullptr); + back_.emplace_back(spec); +} + + +void Layered::push_front(Spec* spec) { + ASSERT(spec != nullptr); + front_.emplace_back(spec); +} + + +void Layered::print(std::ostream& out) const { + JSON j(out); + j.startObject(); + + j << "hide"; + j.startList(); + for (const auto& name : hide_) { + j << name; + } + j.endList(); + + j << "before"; + j.startList(); + for (const auto& spec : front_) { + spec->json(j); + } + j.endList(); + + j << "spec"; + spec_.json(j); + + j << "after"; + j.startList(); + for (const auto& spec : back_) { + spec->json(j); + } + j.endList(); + + j.endObject(); +} + + +void Layered::json(JSON&) const { + NOTIMP; +} + + +} // namespace eckit::geo::spec diff --git a/src/eckit/geo/spec/Layered.h b/src/eckit/geo/spec/Layered.h new file mode 100644 index 000000000..a0fb3c3a2 --- /dev/null +++ b/src/eckit/geo/spec/Layered.h @@ -0,0 +1,95 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include +#include + +#include "eckit/geo/Spec.h" + + +namespace eckit::geo::spec { + + +class Layered final : public Spec { +public: + // -- Constructors + + Layered(); + explicit Layered(const Spec&); + + // -- Methods + + void hide(const std::string&); + void unhide(const std::string&); + void push_back(Spec*); + void push_front(Spec*); + + // -- Overridden methods + + bool has(const std::string& name) const override { + return !hide_.contains(name) + && (std::any_of(front_.begin(), front_.end(), + [&](const decltype(front_)::value_type& c) { return c->has(name); }) + || spec_.has(name) + || std::any_of(back_.begin(), back_.end(), + [&](const decltype(back_)::value_type& c) { return c->has(name); })); + } + + bool get(const std::string& name, std::string& value) const override { return get_t(name, value); } + bool get(const std::string& name, bool& value) const override { return get_t(name, value); } + bool get(const std::string& name, int& value) const override { return get_t(name, value); } + bool get(const std::string& name, long& value) const override { return get_t(name, value); } + bool get(const std::string& name, long long& value) const override { return get_t(name, value); } + bool get(const std::string& name, size_t& value) const override { return get_t(name, value); } + bool get(const std::string& name, float& value) const override { return get_t(name, value); } + bool get(const std::string& name, double& value) const override { return get_t(name, value); } + bool get(const std::string& name, std::vector& value) const override { return get_t(name, value); } + bool get(const std::string& name, std::vector& value) const override { return get_t(name, value); } + bool get(const std::string& name, std::vector& value) const override { return get_t(name, value); } + bool get(const std::string& name, std::vector& value) const override { return get_t(name, value); } + bool get(const std::string& name, std::vector& value) const override { return get_t(name, value); } + bool get(const std::string& name, std::vector& value) const override { return get_t(name, value); } + bool get(const std::string& name, std::vector& value) const override { return get_t(name, value); } + +private: + // -- Members + + struct : std::unordered_set { + bool contains(const value_type& name) const { return find(name) != end(); } + } hide_; + + const Spec& spec_; + std::vector> front_; + std::vector> back_; + + // -- Methods + + template + bool get_t(const std::string& name, T& value) const { + return !hide_.contains(name) + && (std::any_of(front_.rbegin(), front_.rend(), + [&](const decltype(front_)::value_type& c) { return c->get(name, value); }) + || spec_.get(name, value) + || std::any_of(back_.begin(), back_.end(), + [&](const decltype(back_)::value_type& c) { return c->get(name, value); })); + } + + // -- Overridden methods + + void print(std::ostream&) const override; + void json(JSON&) const override; +}; + + +} // namespace eckit::geo::spec diff --git a/src/eckit/geo/util.cc b/src/eckit/geo/util.cc new file mode 100644 index 000000000..5937054c6 --- /dev/null +++ b/src/eckit/geo/util.cc @@ -0,0 +1,25 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/util.h" + + +namespace eckit::geo::util { + + +template <> +pl_type pl_convert(const pl_type& pl) { + return pl; +} + + +} // namespace eckit::geo::util diff --git a/src/eckit/geo/util.h b/src/eckit/geo/util.h new file mode 100644 index 000000000..9e4557217 --- /dev/null +++ b/src/eckit/geo/util.h @@ -0,0 +1,77 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include +#include +#include +#include +#include + + +namespace eckit::geo { + + +using difference_type = std::make_signed_t; +using pl_type = std::vector; + + +namespace util { + + +constexpr double DEGREE_TO_RADIAN = M_PI / 180.; +constexpr double RADIAN_TO_DEGREE = M_1_PI * 180.; + + +template +pl_type pl_convert(const T& pl) { + ASSERT(!pl.empty()); + + pl_type _pl(pl.size()); + std::transform(pl.begin(), pl.end(), _pl.begin(), + [](typename T::value_type p) { return static_cast(p); }); + return _pl; +} + + +template <> +pl_type pl_convert(const pl_type&); + + +std::vector arange(double start, double stop, double step); + + +const std::vector& gaussian_latitudes(size_t N, bool increasing); + + +std::vector linspace(double start, double stop, size_t num, bool endpoint); + + +std::pair monotonic_crop(const std::vector&, double min, double max, + double eps); + + +bool reduced_classical_pl_known(size_t N); + + +const pl_type& reduced_classical_pl(size_t N); + + +const pl_type& reduced_octahedral_pl(size_t N); + + +} // namespace util + + +} // namespace eckit::geo diff --git a/src/eckit/geo/util/arange.cc b/src/eckit/geo/util/arange.cc new file mode 100644 index 000000000..4065acf03 --- /dev/null +++ b/src/eckit/geo/util/arange.cc @@ -0,0 +1,39 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include + +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::util { + + +std::vector arange(double start, double stop, double step) { + if (types::is_approximately_equal(step, 0.) || types::is_approximately_equal(start, stop) + || (stop - start) * step < 0.) { + std::vector l(1, start); + return l; + } + + const auto num = static_cast((stop - start) / step) + 1; + + std::vector l(num); + std::generate_n(l.begin(), num, + [start, step, n = 0ULL]() mutable { return start + static_cast(n++) * step; }); + + return l; +} + + +} // namespace eckit::geo::util diff --git a/src/eckit/geo/util/bounding_box.cc b/src/eckit/geo/util/bounding_box.cc new file mode 100644 index 000000000..c4c835e41 --- /dev/null +++ b/src/eckit/geo/util/bounding_box.cc @@ -0,0 +1,311 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include +#include +#include +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/Point.h" +#include "eckit/geo/Projection.h" +#include "eckit/geo/area/BoundingBox.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::util { + + +PointLonLat longitude_in_range(double reference, const PointLonLat& p) { + // keep longitude difference (to reference) range below +-180 degree + auto lon = p.lon; + while (lon > reference + 180.) { + lon -= 360.; + } + while (lon <= reference - 180.) { + lon += 360.; + } + return {lon, p.lat}; +} + + +struct BoundLonLat { + BoundLonLat(PointLonLat min, PointLonLat max) : min_(min), max_(max) {} + + explicit operator area::BoundingBox() const { return {max_.lat, min_.lon, min_.lat, max_.lon}; } + + void extend(PointLonLat p, PointLonLat eps) { + ASSERT(0. <= eps.lon && 0. <= eps.lat); + + auto sub = p - eps; + auto add = p + eps; + min_ = first_ ? sub : PointLonLat::componentsMin(min_, sub); + max_ = first_ ? add : PointLonLat::componentsMax(max_, add); + first_ = false; + + min_ = {min_.lon, std::max(min_.lat, -90.)}; + max_ = {std::min(max_.lon, min_.lon + 360.), std::min(max_.lat, 90.)}; + ASSERT(min_.lon <= max_.lon && min_.lat <= max_.lat); + + includesSouthPole(types::is_approximately_equal(min_.lat, -90.)); + includesNorthPole(types::is_approximately_equal(max_.lat, 90.)); + crossesDateLine(types::is_approximately_equal(max_.lon - min_.lon, 360.)); + } + + bool crossesDateLine(bool yes) { + if ((crossesDateLine_ = crossesDateLine_ || yes)) { + max_ = {min_.lon + 360., max_.lat}; + } + return crossesDateLine_; + } + + bool includesNorthPole(bool yes) { + if ((includesNorthPole_ = includesNorthPole_ || yes)) { + max_ = {max_.lon, 90.}; + } + crossesDateLine(includesNorthPole_); + return includesNorthPole_; + } + + bool includesSouthPole(bool yes) { + if ((includesSouthPole_ = includesSouthPole_ || yes)) { + min_ = {min_.lon, -90.}; + } + crossesDateLine(includesSouthPole_); + return includesSouthPole_; + } + + bool crossesDateLine() const { return crossesDateLine_; } + bool includesNorthPole() const { return includesNorthPole_; } + bool includesSouthPole() const { return includesSouthPole_; } + +private: + PointLonLat min_; + PointLonLat max_; + bool crossesDateLine_ = false; + bool includesNorthPole_ = false; + bool includesSouthPole_ = false; + bool first_ = true; +}; + + +struct Derivate { + Derivate(const Projection& p, Point2 A, Point2 B, double h, double refLongitude = 0.) : + projection_(p), H_{Point2::normalize(B - A) * h}, invnH_(1. / Point2::norm(H_)), refLongitude_(refLongitude) {} + + virtual ~Derivate() = default; + + Derivate(const Derivate&) = delete; + Derivate(Derivate&&) = delete; + void operator=(const Derivate&) = delete; + void operator=(Derivate&&) = delete; + + virtual PointLonLat d(Point2) const = 0; + + PointLonLat f(const Point2& p) const { + return longitude_in_range(refLongitude_, std::get(projection_.inv(p))); + } + + inline const Point2& H() const { return H_; } + inline double invnH() const { return invnH_; } + +private: + const Projection& projection_; + const Point2 H_; + const double invnH_; + const double refLongitude_; +}; + + +struct DerivateForwards final : Derivate { + using Derivate::Derivate; + PointLonLat d(Point2 P) const override { return (f(P + H()) - f(P)) * invnH(); } +}; + + +struct DerivateBackwards final : Derivate { + using Derivate::Derivate; + PointLonLat d(Point2 P) const override { return (f(P) - f(P - H())) * invnH(); } +}; + + +struct DerivateCentral final : Derivate { + DerivateCentral(const Projection& p, Point2 A, Point2 B, double h, double refLongitude) : + Derivate(p, A, B, h, refLongitude), H2_{H() * 0.5} {} + const Point2 H2_; + PointLonLat d(Point2 P) const override { return (f(P + H2_) - f(P - H2_)) * invnH(); } +}; + + +struct DerivateFactory { + static const Derivate* build(const std::string& type, const Projection& p, Point2 A, Point2 B, double h, + double refLongitude = 0.) { + ASSERT(0. < h); + + if (A.distance2(B) < h * h) { + struct DerivateDegenerate final : Derivate { + using Derivate::Derivate; + PointLonLat d(Point2) const override { return {99, 99}; } // FIXME + }; + return new DerivateDegenerate(p, A, B, h, refLongitude); + } + + return type == "forwards" ? static_cast(new DerivateForwards(p, A, B, h, refLongitude)) + : type == "backwards" ? static_cast(new DerivateBackwards(p, A, B, h, refLongitude)) + : type == "central" ? static_cast(new DerivateCentral(p, A, B, h, refLongitude)) + : throw BadValue("DerivateFactory: unknown method", Here()); + } + + static void list(std::ostream& out) { return instance().list_(out); } + +private: + static DerivateFactory& instance() { + static DerivateFactory obj; + return obj; + } + + // This is 'const' as Grid should always be immutable + const Derivate* build_(const std::string& type, const Projection& p, Point2 A, Point2 B, double h, + double refLongitude) const; + + void list_(std::ostream&) const; +}; + + +area::BoundingBox bounding_box(Point2 min, Point2 max, Projection& projection) { + using types::is_strictly_greater; + + + // 0. setup + + // use central longitude as absolute reference (keep points within +-180 longitude range) + const Point2 centre_xy{(min.X + max.X) / 2., (min.Y + max.Y) / 2.}; + const auto centre_ll = std::get(projection.inv(centre_xy)); // asserts fwd(PointLonLat) -> Point2 + const auto centre_lon = centre_ll.lon; + + const std::string derivative_type = "central"; + constexpr double h_ll = 0.5e-6; // precision to microdegrees + constexpr double h = 0.5e-1; // precision to decimeters + constexpr size_t Niter = 100; + + + // 1. determine box from projected corners + + struct : public std::pair { + using pair::pair; + bool contains(const Point2& P) const { + return (first.X < P.X && P.X < second.X) && (first.Y < P.Y && P.Y < second.Y); + } + } rect(min, max); + + const std::pair segments[] = {{{min.X, max.Y}, {max.X, max.Y}}, + {{max.X, max.Y}, {max.X, min.Y}}, + {{max.X, min.Y}, {min.X, min.Y}}, + {{min.X, min.Y}, {min.X, max.Y}}}; + + BoundLonLat bounds(centre_ll, centre_ll); + for (const auto& [A, dummy] : segments) { + auto q = longitude_in_range(centre_lon, std::get(projection.inv(A))); + bounds.extend(q, PointLonLat{h_ll, h_ll}); + } + + + // 2. locate latitude extrema by checking if poles are included (in the un-projected frame) and if not, find extrema + // not at the corners by refining iteratively + + if (!bounds.includesNorthPole()) { + bounds.includesNorthPole(rect.contains(std::get(projection.fwd(PointLonLat{0., 90. - h_ll})))); + } + + if (!bounds.includesSouthPole()) { + bounds.includesSouthPole(rect.contains(std::get(projection.fwd(PointLonLat{0., -90. + h_ll})))); + } + + for (auto [A, B] : segments) { + if (!bounds.includesNorthPole() || !bounds.includesSouthPole()) { + std::unique_ptr derivate( + DerivateFactory::build(derivative_type, projection, A, B, h, centre_lon)); + + double dAdy = derivate->d(A).lat; + double dBdy = derivate->d(B).lat; + + if (!is_strictly_greater(dAdy * dBdy, 0.)) { + PointLonLat H{0, h_ll}; + + for (size_t cnt = 0; cnt < Niter; ++cnt) { + Point2 M = Point2::middle(A, B); + double dMdy = derivate->d(M).lat; + if (is_strictly_greater(dAdy * dMdy, 0.)) { + A = M; + dAdy = dMdy; + } + else if (is_strictly_greater(dBdy * dMdy, 0.)) { + B = M; + dBdy = dMdy; + } + else { + break; + } + } + + // update extrema, extended by 'a small amount' (arbitrary) + bounds.extend(std::get(projection.inv(Point2::middle(A, B))), H); + } + } + } + + + // 3. locate longitude extrema not at the corners by refining iteratively + + for (auto [A, B] : segments) { + if (!bounds.crossesDateLine()) { + std::unique_ptr derivate( + DerivateFactory::build(derivative_type, projection, A, B, h, centre_lon)); + + double dAdx = derivate->d(A).lon; + double dBdx = derivate->d(B).lon; + + if (!is_strictly_greater(dAdx * dBdx, 0.)) { + PointLonLat H{h_ll, 0}; + + for (size_t cnt = 0; cnt < Niter; ++cnt) { + Point2 M = Point2::middle(A, B); + double dMdx = derivate->d(M).lon; + + if (is_strictly_greater(dAdx * dMdx, 0.)) { + A = M; + dAdx = dMdx; + } + else if (is_strictly_greater(dBdx * dMdx, 0.)) { + B = M; + dBdx = dMdx; + } + else { + break; + } + } + + // update extrema, extended by 'a small amount' (arbitrary) + bounds.extend(std::get(projection.inv(Point2::middle(A, B))), H); + } + } + } + + + // 4. return bounding box + return area::BoundingBox{bounds}; +} + + +} // namespace eckit::geo::util diff --git a/src/eckit/geo/util/gaussian_latitudes.cc b/src/eckit/geo/util/gaussian_latitudes.cc new file mode 100644 index 000000000..09a565758 --- /dev/null +++ b/src/eckit/geo/util/gaussian_latitudes.cc @@ -0,0 +1,113 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include +#include +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/Cache.h" +#include "eckit/geo/util.h" + + +namespace eckit::geo::util { + + +const std::vector& gaussian_latitudes(size_t N, bool increasing) { + ASSERT(N > 0); + + using cache_t = CacheT, std::vector>; + const cache_t::key_type key{N, increasing}; + + static cache_t cache; + if (cache.contains(key)) { + return cache[key]; + } + + std::vector lats(2 * N); + + + // Fourier coefficients of series expansion for the ordinary Legendre polynomials + std::vector zzfn(N + 1); + { + // Belousov, Swarztrauber use zfn(0)=std::sqrt(2.) + // IFS normalisation chosen to be 0.5*Integral(Pnm**2) = 1 + std::vector zfn(2 * N + 1, 2.); + + for (size_t i = 1; i <= 2 * N; ++i) { + for (size_t j = 1; j <= i; ++j) { + zfn[i] *= std::sqrt(1. - 0.25 / (static_cast(j * j))); + } + + for (size_t j = 2; j <= i - (i % 2); j += 2) { + zfn[i - j] = zfn[i - j + 2] * static_cast((j - 1) * (2 * i - j + 2)) + / static_cast(j * (2 * i - j + 1)); + } + } + + for (size_t i = 0; i <= N; ++i) { + zzfn[i] = zfn[i * 2]; + } + } + + + // Newton loop (per latitude) to find 0 of Legendre polynomial of degree N (GAWL) + constexpr size_t Nmax = 20; + constexpr auto eps = std::numeric_limits::epsilon() * 1000.; + + for (size_t i = 0; i < N; ++i) { + // First guess for colatitude [rad] + double z = static_cast(4 * (i + 1) - 1) * M_PI / static_cast(4 * 2 * N + 2); + double x = (z + 1. / (std::tan(z) * static_cast(8 * (2 * N) * (2 * N)))); + + auto converged = false; + + for (size_t n = 0; n < Nmax; ++n) { + auto f = 0.5 * zzfn[0]; // normalised ordinary Legendre polynomial == \overbar{P_n}^0 + auto fp = 0.; // normalised derivative == d/d\theta(\overbar{P_n}^0) + + for (size_t i = 1; i <= N; ++i) { + const auto i2 = static_cast(i * 2); + f += zzfn[i] * std::cos(i2 * x); + fp -= zzfn[i] * std::sin(i2 * x) * i2; + } + + auto dx = -f / fp; + x += dx; + + if (converged) { + break; + } + + converged = std::abs(dx) <= eps; + } + + if (!converged) { + throw BadValue("Could not calculate latitude within accuracy/iterations: " + std::to_string(eps) + "/" + + std::to_string(Nmax), + Here()); + } + + // Convert colatitude [rad] to latitude [degree], symmetry + const auto j = 2 * N - 1 - i; + lats[i] = (increasing ? (x - M_PI_2) : (M_PI_2 - x)) * RADIAN_TO_DEGREE; + lats[j] = -lats[i]; + } + + + return (cache[key] = std::move(lats)); +} + + +} // namespace eckit::geo::util diff --git a/src/eckit/geo/util/linspace.cc b/src/eckit/geo/util/linspace.cc new file mode 100644 index 000000000..5f8948a67 --- /dev/null +++ b/src/eckit/geo/util/linspace.cc @@ -0,0 +1,35 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include + + +namespace eckit::geo::util { + + +std::vector linspace(double start, double stop, size_t num, bool endpoint) { + if (num == 0) { + return {}; + } + + const auto step = num > 1 ? (stop - start) / static_cast(endpoint ? (num - 1) : num) : 0; + + std::vector l(num); + std::generate_n(l.begin(), num, + [start, step, n = 0ULL]() mutable { return start + static_cast(n++) * step; }); + + return l; +} + + +} // namespace eckit::geo::util diff --git a/src/eckit/geo/util/monotonic_crop.cc b/src/eckit/geo/util/monotonic_crop.cc new file mode 100644 index 000000000..a6f30c5d3 --- /dev/null +++ b/src/eckit/geo/util/monotonic_crop.cc @@ -0,0 +1,59 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include +#include +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::util { + + +using difference_type = std::make_signed_t; + + +std::pair monotonic_crop(const std::vector& values, double min, double max, + double eps) { + if (values.empty() || min > max) { + return {}; + } + + auto b = values.begin(); + auto e = values.end(); + + // monotonically increasing + const auto increasing = values.size() == 1 || values.front() < values.back(); + if (increasing) { + ASSERT(std::is_sorted(b, e)); + + auto lt + = [eps](double a, double b) { return a < b && (0. == eps || !types::is_approximately_equal(a, b, eps)); }; + + return {std::distance(b, std::lower_bound(b, e, min, lt)), std::distance(b, std::upper_bound(b, e, max, lt))}; + } + + + // monotonically non-increasing + ASSERT(std::is_sorted(values.rbegin(), values.rend())); + + auto gt = [eps](double a, double b) { return a > b && (0. == eps || !types::is_approximately_equal(a, b, eps)); }; + + return {std::distance(b, std::lower_bound(b, e, max, gt)), std::distance(b, std::upper_bound(b, e, min, gt))}; +} + + +} // namespace eckit::geo::util diff --git a/src/eckit/geo/util/mutex.h b/src/eckit/geo/util/mutex.h new file mode 100644 index 000000000..87ed8f477 --- /dev/null +++ b/src/eckit/geo/util/mutex.h @@ -0,0 +1,58 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#define ECKIT_GEO_ECKIT_THREADS + +#if defined(ECKIT_GEO_ECKIT_THREADS) +#include "eckit/thread/AutoLock.h" +#include "eckit/thread/Mutex.h" +#else +#include +#endif + + +namespace eckit::geo::util { + + +#if defined(ECKIT_GEO_ECKIT_THREADS) + + +using recursive_mutex = eckit::Mutex; + +template +using lock_guard = typename eckit::AutoLock; + +struct once_flag { + pthread_once_t once_ = PTHREAD_ONCE_INIT; +}; + +template +inline void call_once(once_flag& flag, Callable&& fun) { + pthread_once(&(flag.once_), fun); +} + + +#else + + +using std::call_once; +using std::lock_guard; +using std::once_flag; +using std::recursive_mutex; + + +#endif + + +} // namespace eckit::geo::util diff --git a/src/eckit/geo/util/reduced_classical_pl.cc b/src/eckit/geo/util/reduced_classical_pl.cc new file mode 100644 index 000000000..c19a6cd7e --- /dev/null +++ b/src/eckit/geo/util/reduced_classical_pl.cc @@ -0,0 +1,1366 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/Cache.h" +#include "eckit/geo/util.h" + + +namespace eckit::geo::util { + + +static const std::map CLASSICAL_PLS{ + {16, {20, 27, 32, 40, 45, 48, 60, 60, 64, 64, 64, 64, 64, 64, 64, 64}}, + {24, {20, 25, 36, 40, 45, 48, 54, 60, 64, 72, 80, 80, 90, 90, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96}}, + {32, {20, 27, 36, 40, 45, 50, 60, 64, 72, 75, 80, 90, 90, 96, 100, 108, + 108, 120, 120, 120, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}}, + {48, {20, 25, 36, 40, 45, 50, 60, 60, 72, 75, 80, 90, 96, 100, 108, 120, + 120, 120, 128, 135, 144, 144, 160, 160, 160, 160, 160, 180, 180, 180, 180, 180, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192}}, + {64, {20, 25, 36, 40, 45, 54, 60, 64, 72, 75, 80, 90, 96, 100, 108, 120, 120, 125, 135, 135, 144, 150, + 160, 160, 180, 180, 180, 180, 192, 192, 200, 200, 216, 216, 216, 216, 225, 225, 225, 240, 240, 240, 240, 243, + 250, 250, 250, 250, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256}}, + {80, {18, 25, 36, 40, 45, 54, 60, 64, 72, 72, 80, 90, 96, 100, 108, 120, 120, 128, 135, 144, + 144, 150, 160, 160, 180, 180, 180, 192, 192, 200, 200, 216, 216, 216, 225, 225, 240, 240, 240, 256, + 256, 256, 256, 288, 288, 288, 288, 288, 288, 288, 288, 288, 300, 300, 300, 300, 320, 320, 320, 320, + 320, 320, 320, 320, 320, 320, 320, 320, 320, 320, 320, 320, 320, 320, 320, 320, 320, 320, 320, 320}}, + {96, {18, 25, 36, 40, 45, 50, 60, 64, 72, 72, 80, 90, 96, 100, 108, 120, 120, 125, 135, 144, + 144, 150, 160, 160, 180, 180, 180, 192, 192, 200, 200, 216, 216, 225, 225, 240, 240, 240, 250, 250, + 256, 270, 270, 270, 288, 288, 288, 288, 300, 300, 300, 320, 320, 320, 320, 320, 324, 360, 360, 360, + 360, 360, 360, 360, 360, 360, 360, 360, 375, 375, 375, 375, 375, 375, 375, 384, 384, 384, 384, 384, + 384, 384, 384, 384, 384, 384, 384, 384, 384, 384, 384, 384, 384, 384, 384, 384}}, + {128, {18, 25, 36, 40, 45, 50, 60, 64, 72, 72, 80, 90, 90, 100, 108, 120, 120, 125, 128, 144, 144, 150, + 160, 160, 180, 180, 180, 192, 192, 200, 216, 216, 216, 225, 240, 240, 240, 250, 250, 256, 270, 270, 288, 288, + 288, 300, 300, 320, 320, 320, 320, 324, 360, 360, 360, 360, 360, 360, 360, 375, 375, 375, 375, 384, 384, 400, + 400, 400, 400, 405, 432, 432, 432, 432, 432, 432, 432, 450, 450, 450, 450, 450, 480, 480, 480, 480, 480, 480, + 480, 480, 480, 480, 486, 486, 486, 500, 500, 500, 500, 500, 500, 500, 512, 512, 512, 512, 512, 512, 512, 512, + 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512}}, + {160, + {18, 25, 36, 40, 45, 50, 60, 64, 72, 72, 80, 90, 90, 96, 108, 120, 120, 125, 128, 135, 144, 150, 160, + 160, 180, 180, 180, 192, 192, 200, 216, 216, 225, 225, 240, 240, 243, 250, 256, 270, 270, 288, 288, 288, 300, 300, + 320, 320, 320, 320, 324, 360, 360, 360, 360, 360, 360, 375, 375, 375, 384, 384, 400, 400, 400, 405, 432, 432, 432, + 432, 432, 450, 450, 450, 450, 480, 480, 480, 480, 480, 480, 480, 500, 500, 500, 500, 500, 512, 512, 540, 540, 540, + 540, 540, 540, 540, 540, 576, 576, 576, 576, 576, 576, 576, 576, 576, 576, 600, 600, 600, 600, 600, 600, 600, 600, + 600, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, + 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640}}, + {200, + {18, 25, 36, 40, 45, 50, 60, 64, 72, 72, 75, 81, 90, 96, 100, 108, 120, 125, 128, 135, 144, 150, 160, + 160, 180, 180, 180, 192, 192, 200, 216, 216, 225, 225, 240, 240, 243, 250, 256, 270, 270, 288, 288, 288, 300, 300, + 320, 320, 320, 320, 360, 360, 360, 360, 360, 360, 375, 375, 375, 384, 400, 400, 400, 400, 432, 432, 432, 432, 432, + 450, 450, 450, 480, 480, 480, 480, 480, 480, 486, 500, 500, 500, 512, 512, 512, 540, 540, 540, 540, 540, 576, 576, + 576, 576, 576, 576, 576, 576, 600, 600, 600, 600, 600, 640, 640, 640, 640, 640, 640, 640, 640, 640, 640, 648, 648, + 675, 675, 675, 675, 675, 675, 675, 720, 720, 720, 720, 720, 720, 720, 720, 720, 720, 720, 720, 720, 720, 729, 729, + 729, 750, 750, 750, 750, 750, 750, 750, 750, 768, 768, 768, 768, 768, 768, 768, 768, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800}}, + {256, + {18, 25, 32, 40, 45, 50, 60, 64, 72, 72, 75, 81, 90, 96, 100, 108, 120, 120, 125, + 135, 144, 150, 160, 160, 180, 180, 180, 192, 192, 200, 216, 216, 216, 225, 240, 240, 243, 250, + 256, 270, 270, 288, 288, 288, 300, 300, 320, 320, 320, 324, 360, 360, 360, 360, 360, 360, 375, + 375, 384, 384, 400, 400, 400, 432, 432, 432, 432, 432, 450, 450, 450, 480, 480, 480, 480, 480, + 486, 500, 500, 500, 512, 512, 540, 540, 540, 540, 540, 576, 576, 576, 576, 576, 576, 600, 600, + 600, 600, 600, 640, 640, 640, 640, 640, 640, 640, 640, 648, 675, 675, 675, 675, 675, 675, 720, + 720, 720, 720, 720, 720, 720, 720, 720, 729, 729, 750, 750, 750, 750, 750, 768, 768, 768, 768, + 800, 800, 800, 800, 800, 800, 800, 800, 810, 810, 864, 864, 864, 864, 864, 864, 864, 864, 864, + 864, 864, 864, 864, 864, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 960, 960, 960, + 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, + 972, 972, 972, 972, 972, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, + 1000, 1000, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, + 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, + 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024}}, + {320, + {18, 25, 36, 40, 45, 50, 60, 64, 72, 72, 75, 81, 90, 96, 100, 108, 120, 120, 125, + 135, 144, 144, 150, 160, 180, 180, 180, 192, 192, 200, 216, 216, 216, 225, 240, 240, 240, 250, + 256, 270, 270, 288, 288, 288, 300, 300, 320, 320, 320, 324, 360, 360, 360, 360, 360, 360, 375, + 375, 384, 384, 400, 400, 405, 432, 432, 432, 432, 450, 450, 450, 480, 480, 480, 480, 480, 486, + 500, 500, 500, 512, 512, 540, 540, 540, 540, 540, 576, 576, 576, 576, 576, 576, 600, 600, 600, + 600, 640, 640, 640, 640, 640, 640, 640, 648, 648, 675, 675, 675, 675, 720, 720, 720, 720, 720, + 720, 720, 720, 720, 729, 750, 750, 750, 750, 768, 768, 768, 768, 800, 800, 800, 800, 800, 800, + 810, 810, 864, 864, 864, 864, 864, 864, 864, 864, 864, 864, 864, 900, 900, 900, 900, 900, 900, + 900, 900, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 972, 972, 1000, + 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1024, 1024, 1024, 1024, 1024, 1024, 1080, 1080, 1080, 1080, 1080, 1080, + 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, + 1125, 1125, 1125, 1152, 1152, 1152, 1152, 1152, 1152, 1152, 1152, 1152, 1200, 1200, 1200, 1200, 1200, 1200, 1200, + 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1215, 1215, 1215, 1215, 1215, 1215, 1215, 1280, + 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, + 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, + 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, + 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280}}, + {400, + {18, 25, 32, 40, 45, 50, 60, 60, 72, 72, 75, 81, 90, 96, 100, 108, 120, 120, 125, + 128, 144, 144, 150, 160, 160, 180, 180, 192, 192, 200, 200, 216, 216, 225, 240, 240, 240, 250, + 250, 256, 270, 288, 288, 288, 300, 300, 320, 320, 320, 324, 360, 360, 360, 360, 360, 360, 375, + 375, 384, 400, 400, 400, 405, 432, 432, 432, 432, 450, 450, 450, 480, 480, 480, 480, 480, 486, + 500, 500, 512, 512, 540, 540, 540, 540, 540, 576, 576, 576, 576, 576, 576, 600, 600, 600, 600, + 640, 640, 640, 640, 640, 640, 640, 648, 675, 675, 675, 675, 675, 720, 720, 720, 720, 720, 720, + 720, 729, 729, 750, 750, 750, 750, 768, 768, 768, 800, 800, 800, 800, 800, 800, 810, 864, 864, + 864, 864, 864, 864, 864, 864, 864, 864, 900, 900, 900, 900, 900, 900, 900, 960, 960, 960, 960, + 960, 960, 960, 960, 960, 960, 960, 960, 972, 972, 1000, 1000, 1000, 1000, 1000, 1000, 1024, 1024, 1024, + 1024, 1024, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1125, 1125, 1125, 1125, 1125, 1125, + 1125, 1125, 1125, 1152, 1152, 1152, 1152, 1152, 1152, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, + 1200, 1215, 1215, 1215, 1215, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, + 1280, 1280, 1296, 1296, 1296, 1296, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, + 1350, 1350, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, + 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1458, 1458, 1458, 1458, 1458, 1458, 1458, 1500, + 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1536, 1536, 1536, + 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1600, 1600, 1600, 1600, 1600, 1600, + 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, + 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, + 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, + 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, + 1600}}, + {512, + {18, 25, 32, 40, 45, 50, 60, 60, 72, 72, 75, 81, 90, 96, 96, 100, 108, 120, 125, + 128, 135, 144, 150, 160, 160, 180, 180, 180, 192, 192, 200, 216, 216, 225, 225, 240, 240, 243, + 250, 256, 270, 270, 288, 288, 288, 300, 320, 320, 320, 320, 360, 360, 360, 360, 360, 360, 375, + 375, 384, 384, 400, 400, 400, 432, 432, 432, 432, 450, 450, 450, 480, 480, 480, 480, 480, 486, + 500, 500, 512, 512, 540, 540, 540, 540, 540, 576, 576, 576, 576, 576, 576, 600, 600, 600, 640, + 640, 640, 640, 640, 640, 640, 648, 675, 675, 675, 675, 675, 720, 720, 720, 720, 720, 720, 720, + 729, 729, 750, 750, 750, 768, 768, 768, 800, 800, 800, 800, 800, 800, 810, 864, 864, 864, 864, + 864, 864, 864, 864, 864, 864, 900, 900, 900, 900, 900, 900, 960, 960, 960, 960, 960, 960, 960, + 960, 960, 960, 960, 972, 972, 1000, 1000, 1000, 1000, 1000, 1024, 1024, 1024, 1024, 1080, 1080, 1080, 1080, + 1080, 1080, 1080, 1080, 1080, 1080, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1152, 1152, 1152, 1152, + 1152, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1215, 1215, 1215, 1280, 1280, 1280, 1280, 1280, 1280, + 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1296, 1296, 1296, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, + 1350, 1350, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, + 1440, 1440, 1458, 1458, 1458, 1458, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1536, 1536, 1536, + 1536, 1536, 1536, 1536, 1536, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, + 1600, 1600, 1620, 1620, 1620, 1620, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, + 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1800, 1800, + 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, + 1800, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, + 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, + 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1944, 1944, 1944, 1944, 1944, 1944, 1944, 1944, 1944, 1944, 1944, 2000, + 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, + 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2025, 2025, 2025, 2025, 2025, 2025, 2025, + 2025, 2025, 2025, 2025, 2025, 2025, 2025, 2025, 2025, 2025, 2025, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, + 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, + 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, + 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048}}, + {576, + {18, 25, 32, 40, 45, 50, 60, 60, 72, 72, 75, 81, 90, 96, 96, 100, 108, 120, 120, + 125, 135, 144, 150, 160, 160, 180, 180, 180, 192, 192, 200, 216, 216, 225, 225, 240, 240, 243, + 250, 256, 270, 270, 288, 288, 288, 300, 300, 320, 320, 320, 360, 360, 360, 360, 360, 360, 375, + 375, 384, 384, 400, 400, 400, 432, 432, 432, 432, 450, 450, 450, 480, 480, 480, 480, 480, 486, + 500, 500, 512, 512, 540, 540, 540, 540, 540, 576, 576, 576, 576, 576, 600, 600, 600, 600, 640, + 640, 640, 640, 640, 640, 640, 648, 675, 675, 675, 675, 675, 720, 720, 720, 720, 720, 720, 720, + 729, 750, 750, 750, 750, 768, 768, 768, 800, 800, 800, 800, 800, 810, 810, 864, 864, 864, 864, + 864, 864, 864, 864, 864, 900, 900, 900, 900, 900, 900, 960, 960, 960, 960, 960, 960, 960, 960, + 960, 960, 972, 972, 972, 1000, 1000, 1000, 1000, 1000, 1024, 1024, 1024, 1024, 1080, 1080, 1080, 1080, 1080, + 1080, 1080, 1080, 1080, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1152, 1152, 1152, 1152, 1152, 1200, 1200, + 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1215, 1215, 1215, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, + 1280, 1280, 1280, 1296, 1296, 1296, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1440, 1440, 1440, + 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1458, 1458, 1458, 1458, + 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1600, 1600, 1600, + 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1620, 1620, 1620, 1620, 1728, 1728, 1728, 1728, + 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, + 1728, 1728, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, + 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, + 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1944, 1944, 1944, 1944, 1944, 1944, + 1944, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2025, + 2025, 2025, 2025, 2025, 2025, 2025, 2025, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2160, 2160, 2160, 2160, 2160, + 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, + 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, + 2187, 2187, 2187, 2187, 2187, 2187, 2187, 2187, 2187, 2187, 2187, 2187, 2250, 2250, 2250, 2250, 2250, 2250, 2250, + 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, + 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, + 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, + 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, + 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, + 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, + 2304, 2304, 2304, 2304, 2304, 2304}}, + {640, + {18, 25, 32, 40, 45, 50, 60, 60, 72, 72, 75, 81, 90, 90, 96, 100, 108, 120, 120, + 125, 135, 144, 150, 160, 160, 180, 180, 180, 192, 192, 200, 216, 216, 216, 225, 240, 240, 243, + 250, 256, 270, 270, 288, 288, 288, 300, 300, 320, 320, 320, 360, 360, 360, 360, 360, 360, 375, + 375, 384, 384, 400, 400, 400, 432, 432, 432, 432, 450, 450, 450, 480, 480, 480, 480, 480, 486, + 500, 500, 512, 512, 540, 540, 540, 540, 540, 576, 576, 576, 576, 576, 600, 600, 600, 600, 640, + 640, 640, 640, 640, 640, 640, 648, 675, 675, 675, 675, 720, 720, 720, 720, 720, 720, 720, 720, + 729, 750, 750, 750, 750, 768, 768, 768, 800, 800, 800, 800, 800, 810, 810, 864, 864, 864, 864, + 864, 864, 864, 864, 900, 900, 900, 900, 900, 900, 960, 960, 960, 960, 960, 960, 960, 960, 960, + 960, 960, 972, 972, 1000, 1000, 1000, 1000, 1000, 1024, 1024, 1024, 1024, 1080, 1080, 1080, 1080, 1080, 1080, + 1080, 1080, 1080, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1152, 1152, 1152, 1152, 1152, 1200, 1200, 1200, + 1200, 1200, 1200, 1200, 1200, 1215, 1215, 1215, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, + 1280, 1296, 1296, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1440, 1440, 1440, 1440, 1440, 1440, + 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1458, 1458, 1458, 1458, 1500, 1500, 1500, 1500, + 1500, 1500, 1500, 1500, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, + 1600, 1600, 1600, 1600, 1600, 1620, 1620, 1620, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, + 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1800, 1800, 1800, 1800, 1800, 1800, 1800, + 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, + 1875, 1875, 1875, 1875, 1875, 1875, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1944, 1944, + 1944, 1944, 1944, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2025, 2025, + 2025, 2025, 2025, 2025, 2048, 2048, 2048, 2048, 2048, 2048, 2048, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, + 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, + 2160, 2160, 2160, 2187, 2187, 2187, 2187, 2187, 2187, 2187, 2187, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, + 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2304, 2304, 2304, 2304, 2304, 2304, 2304, + 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, + 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, + 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, + 2430, 2430, 2430, 2430, 2430, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, + 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, + 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, + 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, + 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, + 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, + 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, + 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560}}, + {800, + {18, 25, 32, 40, 45, 50, 60, 60, 72, 72, 75, 80, 90, 90, 96, 100, 108, 120, 120, + 125, 128, 135, 144, 150, 160, 160, 180, 180, 192, 192, 200, 200, 216, 216, 225, 240, 240, 240, + 250, 250, 256, 270, 288, 288, 288, 300, 300, 320, 320, 320, 324, 360, 360, 360, 360, 360, 375, + 375, 375, 384, 400, 400, 400, 405, 432, 432, 432, 432, 450, 450, 450, 480, 480, 480, 480, 486, + 500, 500, 500, 512, 512, 540, 540, 540, 540, 576, 576, 576, 576, 576, 576, 600, 600, 600, 625, + 625, 625, 625, 625, 640, 640, 648, 675, 675, 675, 675, 720, 720, 720, 720, 720, 720, 720, 720, + 729, 750, 750, 750, 768, 768, 768, 800, 800, 800, 800, 800, 800, 810, 864, 864, 864, 864, 864, + 864, 864, 864, 864, 900, 900, 900, 900, 900, 900, 960, 960, 960, 960, 960, 960, 960, 960, 960, + 960, 972, 972, 1000, 1000, 1000, 1000, 1000, 1024, 1024, 1024, 1024, 1080, 1080, 1080, 1080, 1080, 1080, 1080, + 1080, 1080, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1152, 1152, 1152, 1152, 1152, 1200, 1200, 1200, 1200, 1200, + 1200, 1200, 1200, 1215, 1215, 1250, 1250, 1250, 1250, 1250, 1250, 1280, 1280, 1280, 1280, 1280, 1280, 1296, 1296, + 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, + 1440, 1440, 1440, 1440, 1440, 1440, 1458, 1458, 1458, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1536, 1536, + 1536, 1536, 1536, 1536, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1620, 1620, 1620, + 1620, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, + 1728, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1875, 1875, 1875, 1875, + 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, + 1944, 1944, 1944, 1944, 1944, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2025, 2025, 2025, + 2025, 2025, 2048, 2048, 2048, 2048, 2048, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, + 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2187, 2187, 2187, 2187, 2187, 2187, 2250, 2250, + 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2304, 2304, 2304, 2304, 2304, 2304, 2304, + 2304, 2304, 2304, 2304, 2304, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, + 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2500, 2500, 2500, 2500, + 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2560, 2560, 2560, 2560, 2560, + 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2592, 2592, 2592, 2592, 2592, 2592, 2592, 2592, 2700, + 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, + 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, + 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, + 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, + 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2916, 2916, 2916, 2916, 2916, 2916, 2916, 2916, + 2916, 2916, 2916, 2916, 2916, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, + 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, + 3000, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, + 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3125, 3125, + 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, + 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3200, 3200, 3200, 3200, 3200, 3200, 3200, + 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, + 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, + 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, + 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, + 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, + 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, + 3200, 3200}}, + {1024, + {18, 25, 32, 40, 45, 50, 60, 64, 72, 72, 75, 81, 90, 96, 96, 108, 108, 120, 120, + 125, 125, 135, 144, 150, 160, 160, 180, 180, 180, 192, 192, 200, 216, 216, 225, 225, 240, 240, + 243, 250, 256, 270, 270, 288, 288, 288, 300, 300, 320, 320, 320, 360, 360, 360, 360, 360, 360, + 375, 375, 384, 384, 400, 400, 405, 432, 432, 432, 432, 450, 450, 450, 480, 480, 480, 480, 480, + 486, 500, 500, 512, 512, 540, 540, 540, 540, 576, 576, 576, 576, 576, 576, 600, 600, 600, 600, + 625, 625, 625, 625, 640, 640, 648, 675, 675, 675, 675, 675, 720, 720, 720, 720, 720, 720, 720, + 729, 750, 750, 750, 750, 768, 768, 800, 800, 800, 800, 800, 800, 810, 864, 864, 864, 864, 864, + 864, 864, 864, 864, 900, 900, 900, 900, 900, 900, 960, 960, 960, 960, 960, 960, 960, 960, 960, + 972, 972, 1000, 1000, 1000, 1000, 1000, 1024, 1024, 1024, 1024, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, + 1080, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1152, 1152, 1152, 1152, 1152, 1200, 1200, 1200, 1200, 1200, 1200, + 1200, 1215, 1215, 1215, 1250, 1250, 1250, 1250, 1250, 1250, 1280, 1280, 1280, 1280, 1280, 1296, 1296, 1350, 1350, + 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, + 1440, 1440, 1440, 1458, 1458, 1458, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1536, 1536, 1536, 1536, 1536, + 1536, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1620, 1620, 1620, 1728, 1728, 1728, 1728, + 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1800, 1800, 1800, 1800, + 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, + 1875, 1875, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1920, 1944, 1944, 1944, 1944, 1944, 2000, 2000, 2000, 2000, + 2000, 2000, 2000, 2000, 2000, 2000, 2025, 2025, 2025, 2025, 2048, 2048, 2048, 2048, 2048, 2160, 2160, 2160, 2160, + 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2187, 2187, 2187, + 2187, 2187, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2304, 2304, 2304, 2304, 2304, + 2304, 2304, 2304, 2304, 2304, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, + 2400, 2400, 2400, 2400, 2400, 2430, 2430, 2430, 2430, 2430, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, + 2500, 2500, 2500, 2500, 2500, 2500, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2592, + 2592, 2592, 2592, 2592, 2592, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, + 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, + 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, + 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2916, 2916, 2916, 2916, 2916, 2916, 2916, 2916, 3000, 3000, + 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3072, 3072, + 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3125, 3125, 3125, 3125, + 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, + 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3375, + 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, + 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3456, 3456, 3456, + 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, + 3456, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, + 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, + 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3645, 3645, 3645, 3645, 3645, 3645, 3645, 3645, 3645, 3645, 3645, 3645, + 3645, 3645, 3645, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, + 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, + 3750, 3750, 3750, 3750, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + 3840, 3840, 3840, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, + 3888, 3888, 3888, 3888, 3888, 3888, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, + 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, + 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, + 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4050, 4050, 4050, 4050, 4050, + 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, + 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4096, 4096, 4096, 4096, + 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, + 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, + 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, + 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, + 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, + 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096}}, + {1280, + {18, 25, 32, 40, 45, 50, 60, 64, 72, 72, 75, 81, 90, 96, 96, 108, 108, 120, 120, + 120, 125, 135, 135, 144, 144, 160, 160, 180, 180, 180, 192, 200, 200, 216, 216, 225, 240, 240, + 240, 250, 250, 256, 270, 288, 288, 288, 300, 300, 320, 320, 320, 324, 360, 360, 360, 360, 360, + 375, 375, 375, 384, 400, 400, 400, 432, 432, 432, 432, 432, 450, 450, 480, 480, 480, 480, 480, + 486, 500, 500, 512, 512, 540, 540, 540, 540, 540, 576, 576, 576, 576, 576, 600, 600, 600, 600, + 625, 625, 625, 625, 640, 640, 640, 648, 675, 675, 675, 675, 720, 720, 720, 720, 720, 720, 720, + 729, 729, 750, 750, 750, 768, 768, 768, 800, 800, 800, 800, 800, 810, 864, 864, 864, 864, 864, + 864, 864, 864, 864, 900, 900, 900, 900, 900, 900, 960, 960, 960, 960, 960, 960, 960, 960, 960, + 972, 972, 1000, 1000, 1000, 1000, 1000, 1024, 1024, 1024, 1024, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, + 1080, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1152, 1152, 1152, 1152, 1200, 1200, 1200, 1200, 1200, 1200, 1200, + 1200, 1215, 1215, 1250, 1250, 1250, 1250, 1250, 1250, 1280, 1280, 1280, 1280, 1280, 1296, 1296, 1296, 1350, 1350, + 1350, 1350, 1350, 1350, 1350, 1350, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, + 1440, 1440, 1458, 1458, 1458, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1536, 1536, 1536, 1536, 1536, 1536, 1600, + 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1620, 1620, 1620, 1728, 1728, 1728, 1728, 1728, 1728, + 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1800, 1800, 1800, 1800, 1800, 1800, 1800, + 1800, 1800, 1800, 1800, 1800, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1920, + 1920, 1920, 1920, 1920, 1920, 1920, 1944, 1944, 1944, 1944, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, + 2000, 2025, 2025, 2025, 2025, 2048, 2048, 2048, 2048, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, + 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2187, 2187, 2187, 2187, 2250, 2250, 2250, 2250, 2250, + 2250, 2250, 2250, 2250, 2250, 2250, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2400, 2400, 2400, + 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2430, 2430, 2430, 2430, 2430, + 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2560, 2560, 2560, 2560, 2560, 2560, + 2560, 2560, 2560, 2560, 2560, 2592, 2592, 2592, 2592, 2592, 2592, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, + 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2880, 2880, 2880, 2880, 2880, 2880, 2880, + 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, + 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2916, 2916, 2916, 2916, 2916, 2916, 3000, 3000, 3000, 3000, 3000, + 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3072, 3072, 3072, 3072, 3072, 3072, 3072, + 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3200, 3200, + 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3240, 3240, 3240, 3240, 3240, + 3240, 3240, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, + 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, + 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, + 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, + 3600, 3600, 3600, 3645, 3645, 3645, 3645, 3645, 3645, 3645, 3645, 3645, 3645, 3750, 3750, 3750, 3750, 3750, 3750, + 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3840, + 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + 3840, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 4000, 4000, 4000, 4000, 4000, 4000, 4000, + 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, + 4000, 4000, 4000, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4096, 4096, 4096, 4096, + 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, + 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, + 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, + 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4374, 4374, 4374, 4374, 4374, 4374, + 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, + 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, + 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, + 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, + 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, + 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, + 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, + 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, + 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, + 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, + 4860, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, + 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, + 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, + 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, + 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, + 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, + 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, + 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, + 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, + 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, + 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, + 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, + 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, + 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, + 5120, 5120, 5120, 5120, 5120, 5120, 5120}}, + {1600, + {18, 25, 32, 40, 45, 50, 54, 60, 72, 72, 75, 80, 90, 90, 96, 100, 108, 120, 120, + 120, 125, 128, 135, 144, 144, 150, 160, 160, 162, 180, 180, 180, 192, 192, 216, 216, 225, 240, + 240, 243, 250, 256, 270, 270, 288, 288, 288, 300, 300, 320, 320, 320, 360, 360, 360, 360, 360, + 360, 375, 375, 384, 384, 400, 400, 405, 432, 432, 432, 432, 450, 450, 450, 480, 480, 480, 480, + 480, 486, 500, 500, 512, 512, 540, 540, 540, 540, 576, 576, 576, 576, 576, 576, 600, 600, 600, + 600, 625, 625, 625, 625, 640, 640, 648, 675, 675, 675, 675, 720, 720, 720, 720, 720, 720, 720, + 729, 729, 750, 750, 750, 768, 768, 768, 800, 800, 800, 800, 800, 810, 810, 864, 864, 864, 864, + 864, 864, 864, 864, 900, 900, 900, 900, 900, 900, 960, 960, 960, 960, 960, 960, 960, 960, 960, + 960, 972, 1000, 1000, 1000, 1000, 1000, 1024, 1024, 1024, 1024, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, + 1080, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1152, 1152, 1152, 1152, 1200, 1200, 1200, 1200, 1200, 1200, 1200, + 1200, 1215, 1215, 1250, 1250, 1250, 1250, 1250, 1250, 1280, 1280, 1280, 1280, 1280, 1296, 1296, 1350, 1350, 1350, + 1350, 1350, 1350, 1350, 1350, 1350, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, + 1440, 1440, 1458, 1458, 1458, 1500, 1500, 1500, 1500, 1500, 1500, 1536, 1536, 1536, 1536, 1536, 1536, 1600, 1600, + 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1620, 1620, 1620, 1728, 1728, 1728, 1728, 1728, 1728, 1728, + 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, + 1800, 1800, 1800, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1920, 1920, 1920, + 1920, 1920, 1920, 1920, 1944, 1944, 1944, 1944, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2025, + 2025, 2025, 2025, 2048, 2048, 2048, 2048, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, + 2160, 2160, 2160, 2160, 2160, 2160, 2187, 2187, 2187, 2187, 2187, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, + 2250, 2250, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, + 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2430, 2430, 2430, 2430, 2430, 2500, 2500, 2500, 2500, 2500, + 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2592, 2592, + 2592, 2592, 2592, 2592, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, + 2700, 2700, 2700, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, + 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2916, 2916, 2916, + 2916, 2916, 2916, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3072, + 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3125, 3125, 3125, 3125, 3125, 3125, 3125, + 3125, 3125, 3125, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3240, 3240, + 3240, 3240, 3240, 3240, 3240, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, + 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, + 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, + 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3645, 3645, 3645, 3645, + 3645, 3645, 3645, 3645, 3645, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, + 3750, 3750, 3750, 3750, 3750, 3750, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + 3840, 3840, 3840, 3840, 3840, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 4000, 4000, 4000, 4000, 4000, + 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4050, + 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, + 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, + 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, + 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4374, + 4374, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, + 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, + 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4800, 4800, 4800, 4800, + 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, + 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, + 4800, 4800, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 5000, 5000, + 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, + 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5120, 5120, 5120, 5120, 5120, + 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, + 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5184, 5184, 5184, 5184, 5184, 5184, 5184, 5184, 5184, 5184, 5184, 5184, + 5184, 5184, 5184, 5184, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, + 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, + 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, + 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, + 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, + 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, + 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, + 5625, 5625, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, + 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, + 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, + 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 6000, + 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, + 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, + 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, + 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, + 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, + 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, + 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, + 6144, 6144, 6144, 6144, 6144, 6144, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, + 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, + 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, + 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6400, 6400, 6400, + 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, + 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, + 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, + 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, + 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, + 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, + 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, + 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, + 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, + 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, + 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, + 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, + 6400, 6400, 6400, 6400}}, + {2000, + {18, 25, 32, 40, 45, 50, 60, 60, 72, 72, 75, 81, 90, 96, 96, 108, 108, 120, 120, + 120, 125, 135, 135, 144, 144, 150, 160, 160, 180, 180, 180, 180, 192, 192, 192, 200, 216, 216, + 216, 225, 240, 240, 243, 250, 256, 270, 270, 288, 300, 300, 320, 320, 320, 360, 360, 360, 360, + 360, 360, 375, 375, 384, 400, 400, 400, 405, 432, 432, 432, 432, 450, 450, 450, 480, 480, 480, + 480, 486, 500, 500, 500, 512, 512, 540, 540, 540, 540, 576, 576, 576, 576, 576, 600, 600, 600, + 600, 625, 625, 625, 625, 640, 640, 640, 648, 675, 675, 675, 675, 720, 720, 720, 720, 720, 720, + 720, 729, 750, 750, 750, 750, 768, 768, 768, 800, 800, 800, 800, 800, 810, 864, 864, 864, 864, + 864, 864, 864, 864, 864, 900, 900, 900, 900, 900, 900, 960, 960, 960, 960, 960, 960, 960, 960, + 960, 972, 972, 1000, 1000, 1000, 1000, 1000, 1024, 1024, 1024, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, + 1080, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1152, 1152, 1152, 1152, 1152, 1200, 1200, 1200, 1200, 1200, 1200, + 1200, 1215, 1215, 1215, 1250, 1250, 1250, 1250, 1250, 1280, 1280, 1280, 1280, 1280, 1296, 1296, 1296, 1350, 1350, + 1350, 1350, 1350, 1350, 1350, 1350, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, + 1440, 1440, 1458, 1458, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1536, 1536, 1536, 1536, 1536, 1536, 1600, 1600, + 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1620, 1620, 1620, 1728, 1728, 1728, 1728, 1728, 1728, 1728, + 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, + 1800, 1800, 1800, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1920, 1920, 1920, 1920, + 1920, 1920, 1920, 1944, 1944, 1944, 1944, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2025, 2025, 2025, + 2025, 2048, 2048, 2048, 2048, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, + 2160, 2160, 2160, 2160, 2187, 2187, 2187, 2187, 2187, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, + 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, + 2400, 2400, 2400, 2400, 2400, 2400, 2430, 2430, 2430, 2430, 2430, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, + 2500, 2500, 2500, 2500, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2592, 2592, 2592, 2592, 2592, + 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2880, + 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, + 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2916, 2916, 2916, 2916, 2916, 2916, 3000, 3000, 3000, + 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3072, 3072, 3072, 3072, 3072, 3072, 3072, + 3072, 3072, 3072, 3072, 3072, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3200, 3200, 3200, 3200, 3200, + 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3375, 3375, 3375, 3375, + 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, + 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3600, 3600, 3600, 3600, + 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, + 3600, 3600, 3645, 3645, 3645, 3645, 3645, 3645, 3645, 3645, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, + 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 4000, 4000, 4000, + 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4050, 4050, + 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4320, 4320, 4320, + 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, + 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, + 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, + 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4608, 4608, 4608, 4608, + 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4800, 4800, + 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, + 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4860, 4860, + 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, + 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, + 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, + 5120, 5120, 5120, 5120, 5120, 5184, 5184, 5184, 5184, 5184, 5184, 5184, 5184, 5184, 5184, 5184, 5184, 5184, 5400, + 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, + 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, + 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, + 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, + 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5760, + 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, + 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, + 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, + 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, + 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, + 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, + 6144, 6144, 6144, 6144, 6144, 6144, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, + 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6400, 6400, 6400, 6400, 6400, + 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, + 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6480, 6480, 6480, 6480, + 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6561, 6561, + 6561, 6561, 6561, 6561, 6561, 6561, 6561, 6561, 6561, 6561, 6561, 6561, 6561, 6561, 6561, 6561, 6561, 6561, 6561, + 6561, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, + 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, + 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6912, 6912, + 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, + 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, + 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, + 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, + 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, + 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, + 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, + 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, + 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, + 7290, 7290, 7290, 7290, 7290, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, + 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, + 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, + 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, + 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7680, 7680, 7680, + 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, + 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, + 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, + 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, + 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, + 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, + 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, + 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000}}, + {4000, + {18, 24, 32, 40, 45, 48, 54, 60, 64, 72, 75, 80, 90, 90, 96, 100, + 108, 108, 120, 120, 125, 128, 135, 135, 144, 150, 150, 160, 160, 180, 180, 180, + 180, 192, 192, 192, 200, 216, 216, 216, 216, 225, 225, 240, 240, 240, 243, 250, + 256, 256, 270, 270, 270, 288, 288, 288, 288, 300, 300, 300, 320, 320, 320, 320, + 324, 360, 360, 360, 360, 360, 360, 360, 360, 375, 375, 375, 375, 384, 384, 400, + 400, 400, 405, 405, 432, 432, 432, 432, 432, 432, 450, 450, 450, 450, 480, 480, + 480, 480, 480, 480, 486, 486, 500, 500, 500, 512, 512, 512, 540, 540, 540, 540, + 540, 540, 576, 576, 576, 576, 576, 576, 576, 576, 600, 600, 600, 600, 600, 600, + 625, 625, 625, 625, 625, 625, 640, 640, 640, 648, 648, 675, 675, 675, 675, 675, + 675, 720, 720, 720, 720, 720, 720, 720, 720, 720, 720, 720, 729, 729, 750, 750, + 750, 750, 750, 768, 768, 768, 768, 800, 800, 800, 800, 800, 800, 800, 810, 810, + 864, 864, 864, 864, 864, 864, 864, 864, 864, 864, 864, 864, 864, 900, 900, 900, + 900, 900, 900, 900, 900, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, + 960, 960, 960, 972, 972, 972, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1024, 1024, 1024, + 1024, 1024, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1125, + 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1152, 1152, 1152, 1152, 1152, 1152, + 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1215, 1215, 1215, 1215, 1250, + 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1296, 1296, + 1296, 1296, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1440, + 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, + 1440, 1440, 1440, 1440, 1458, 1458, 1458, 1458, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, + 1500, 1500, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1600, 1600, 1600, 1600, 1600, + 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1620, 1620, 1620, 1620, 1728, 1728, + 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, + 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, + 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1875, 1875, 1875, 1875, 1875, 1875, 1875, + 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1920, 1920, 1920, 1920, 1920, + 1920, 1920, 1920, 1920, 1920, 1920, 1944, 1944, 1944, 1944, 1944, 2000, 2000, 2000, 2000, 2000, + 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2025, 2025, 2025, 2025, 2025, 2025, 2048, + 2048, 2048, 2048, 2048, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, + 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2187, + 2187, 2187, 2187, 2187, 2187, 2187, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, + 2250, 2250, 2250, 2250, 2250, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, + 2304, 2304, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, + 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2430, 2430, 2430, 2500, 2500, 2500, 2500, 2500, + 2500, 2500, 2500, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2592, 2592, 2592, 2700, 2700, 2700, + 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2880, 2880, 2880, 2880, 2880, 2880, + 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, + 2916, 2916, 2916, 2916, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3072, 3072, + 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3200, 3200, + 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3240, 3240, 3240, 3240, 3375, 3375, 3375, 3375, + 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3456, 3456, + 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, + 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3645, 3645, 3645, 3645, 3645, + 3645, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, + 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3888, 3888, 3888, + 3888, 3888, 3888, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, + 4000, 4000, 4000, 4050, 4050, 4050, 4050, 4050, 4050, 4096, 4096, 4096, 4096, 4096, 4096, 4096, + 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, + 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4374, + 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, + 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4608, 4608, 4608, 4608, 4608, 4608, 4608, + 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4800, 4800, 4800, 4800, 4800, 4800, 4800, + 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, + 4800, 4800, 4800, 4800, 4800, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 4860, 5000, 5000, + 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, + 5000, 5000, 5000, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, + 5120, 5120, 5120, 5120, 5120, 5184, 5184, 5184, 5184, 5184, 5184, 5184, 5184, 5184, 5400, 5400, + 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, + 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5625, + 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, + 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, + 5625, 5625, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, + 5760, 5760, 5760, 5760, 5760, 5760, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, + 5832, 5832, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, + 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6075, 6075, 6075, 6075, + 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6144, 6144, 6144, 6144, 6144, 6144, 6144, + 6144, 6144, 6144, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, + 6250, 6250, 6250, 6250, 6250, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, + 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6480, 6480, 6480, 6480, + 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6561, 6561, 6561, 6561, 6561, 6561, 6561, + 6561, 6561, 6561, 6561, 6561, 6561, 6561, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, + 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, + 6750, 6750, 6750, 6750, 6750, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, + 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, + 6912, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, + 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, + 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, + 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, + 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, + 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, + 7500, 7500, 7500, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, + 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, + 7680, 7680, 7680, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, 7776, + 7776, 7776, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8100, 8100, 8100, 8100, 8100, 8100, + 8100, 8100, 8100, 8100, 8100, 8100, 8100, 8100, 8100, 8100, 8100, 8192, 8192, 8192, 8192, 8192, + 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8640, 8640, 8640, 8640, + 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, + 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, + 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, + 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, + 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8748, 8748, 8748, 8748, + 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, + 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, + 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, + 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9216, 9216, 9216, + 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, + 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, + 9216, 9216, 9216, 9216, 9216, 9216, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, + 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, + 9375, 9375, 9375, 9375, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, + 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, + 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, + 9720, 9720, 9720, 9720, 9720, 9720, 9720, 9720, 9720, 9720, 9720, 9720, 9720, 9720, 9720, 9720, + 9720, 9720, 9720, 9720, 9720, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, + 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, + 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, + 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10125, 10125, 10125, + 10125, 10125, 10125, 10125, 10125, 10125, 10125, 10125, 10125, 10125, 10125, 10125, 10125, 10125, 10125, 10125, + 10125, 10125, 10125, 10125, 10125, 10125, 10240, 10240, 10240, 10240, 10240, 10240, 10240, 10240, 10240, 10240, + 10240, 10240, 10240, 10240, 10240, 10240, 10240, 10240, 10240, 10240, 10240, 10240, 10240, 10240, 10368, 10368, + 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, + 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, + 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, + 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, + 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, + 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, + 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10935, 10935, + 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, + 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 11250, 11250, 11250, 11250, 11250, + 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, + 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, + 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, + 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, + 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, + 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, + 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, + 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11664, 11664, 11664, + 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, + 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 12000, 12000, + 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, + 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, + 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, + 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, + 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, + 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, + 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12288, 12288, 12288, 12288, + 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, + 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12500, 12500, 12500, + 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, + 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, + 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, + 12500, 12500, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, + 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, + 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, + 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, + 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, + 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, + 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, + 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 13122, 13122, 13122, 13122, 13122, 13122, + 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, + 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, + 13122, 13122, 13122, 13122, 13122, 13122, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, + 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, + 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, + 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, + 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, + 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, + 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, + 13500, 13500, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, + 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, + 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, + 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, + 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, + 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14580, 14580, + 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, + 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, + 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, + 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, + 14580, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, + 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, + 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, + 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, + 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, + 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, + 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, + 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, + 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, + 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, + 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15360, + 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, + 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, + 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, + 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, + 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, + 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, + 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, + 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, + 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, + 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, + 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, + 15360, 15360, 15360, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, + 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, + 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, + 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, + 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, + 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, + 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, + 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, + 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, + 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, + 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000}}, + {8000, + {16, 24, 30, 36, 40, 45, 54, 60, 64, 72, 72, 75, 81, 90, 90, 96, + 100, 108, 120, 120, 120, 125, 128, 135, 144, 144, 150, 160, 160, 160, 180, 180, + 180, 180, 192, 192, 192, 200, 216, 216, 216, 216, 225, 225, 240, 240, 240, 243, + 250, 250, 256, 270, 270, 270, 288, 288, 288, 288, 300, 300, 300, 320, 320, 320, + 320, 324, 360, 360, 360, 360, 360, 360, 360, 360, 375, 375, 375, 375, 384, 384, + 400, 400, 400, 405, 405, 432, 432, 432, 432, 432, 432, 450, 450, 450, 450, 480, + 480, 480, 480, 480, 480, 480, 486, 500, 500, 500, 512, 512, 512, 540, 540, 540, + 540, 540, 540, 540, 576, 576, 576, 576, 576, 576, 576, 576, 600, 600, 600, 600, + 600, 625, 625, 625, 625, 625, 625, 640, 640, 640, 640, 648, 675, 675, 675, 675, + 675, 675, 675, 720, 720, 720, 720, 720, 720, 720, 720, 720, 720, 729, 729, 750, + 750, 750, 750, 750, 768, 768, 768, 768, 800, 800, 800, 800, 800, 800, 800, 800, + 810, 810, 864, 864, 864, 864, 864, 864, 864, 864, 864, 864, 864, 864, 864, 900, + 900, 900, 900, 900, 900, 900, 900, 960, 960, 960, 960, 960, 960, 960, 960, 960, + 960, 960, 960, 960, 960, 972, 972, 972, 1000, 1000, 1000, 1000, 1000, 1000, 1024, 1024, + 1024, 1024, 1024, 1024, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080, + 1080, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1152, 1152, 1152, 1152, + 1152, 1152, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1215, 1215, 1215, + 1215, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1280, 1280, 1280, 1280, 1280, 1280, 1280, + 1296, 1296, 1296, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1350, + 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, + 1440, 1440, 1440, 1440, 1440, 1458, 1458, 1458, 1458, 1500, 1500, 1500, 1500, 1500, 1500, 1500, + 1500, 1500, 1500, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1600, 1600, 1600, 1600, + 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1620, 1620, 1620, 1620, 1728, + 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, + 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1728, 1800, 1800, 1800, 1800, 1800, 1800, 1800, + 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1875, 1875, 1875, 1875, 1875, 1875, + 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1875, 1920, 1920, 1920, 1920, + 1920, 1920, 1920, 1920, 1920, 1920, 1944, 1944, 1944, 1944, 1944, 1944, 2000, 2000, 2000, 2000, + 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2025, 2025, 2025, 2025, 2025, 2025, 2048, + 2048, 2048, 2048, 2048, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, + 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2160, 2187, + 2187, 2187, 2187, 2187, 2187, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, 2250, + 2250, 2250, 2250, 2250, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, 2304, + 2304, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, + 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2500, + 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2560, + 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2592, 2592, 2592, + 2592, 2592, 2592, 2592, 2592, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, + 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2880, + 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, + 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, + 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2880, 2916, 2916, 2916, 2916, 2916, 2916, + 2916, 2916, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, + 3000, 3000, 3000, 3000, 3000, 3000, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, + 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, + 3125, 3125, 3125, 3125, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, + 3200, 3200, 3200, 3200, 3200, 3200, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3375, + 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, + 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, 3375, + 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, 3456, + 3456, 3456, 3456, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, + 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, + 3600, 3600, 3600, 3600, 3600, 3645, 3645, 3645, 3645, 3645, 3645, 3645, 3645, 3645, 3645, 3645, + 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, + 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3750, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3888, + 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 3888, 4000, 4000, 4000, 4000, 4000, + 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, + 4000, 4000, 4000, 4000, 4000, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, 4050, + 4050, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4320, 4320, 4320, + 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, + 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, + 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, 4320, + 4320, 4320, 4320, 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4374, 4374, + 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, + 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4500, 4608, 4608, + 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, + 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, + 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, + 4800, 4860, 4860, 4860, 4860, 4860, 4860, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, + 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5120, 5120, 5120, 5120, 5120, 5120, 5120, 5120, + 5120, 5120, 5120, 5120, 5120, 5184, 5184, 5184, 5184, 5184, 5184, 5184, 5400, 5400, 5400, 5400, + 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, 5400, + 5400, 5400, 5400, 5400, 5400, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, + 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, 5625, + 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, 5760, + 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 5832, 6000, 6000, 6000, 6000, 6000, 6000, 6000, + 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6075, 6075, + 6075, 6075, 6075, 6075, 6075, 6075, 6075, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6144, 6250, + 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6400, 6400, 6400, + 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, 6400, + 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6480, 6561, 6561, 6561, 6561, 6561, + 6561, 6561, 6561, 6561, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, + 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6750, 6912, 6912, 6912, + 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, 6912, + 6912, 6912, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, + 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, + 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7290, 7290, 7290, 7290, 7290, 7290, 7290, 7290, + 7290, 7290, 7290, 7290, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, + 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, 7500, + 7500, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, + 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7680, 7776, 7776, 7776, 7776, 7776, 7776, + 7776, 7776, 7776, 7776, 7776, 7776, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + 8000, 8000, 8000, 8000, 8000, 8000, 8100, 8100, 8100, 8100, 8100, 8100, 8100, 8100, 8100, 8100, + 8100, 8100, 8100, 8100, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, + 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, + 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, + 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, + 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, 8640, + 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, 8748, + 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, + 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, 9000, + 9000, 9000, 9000, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, + 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, 9216, + 9216, 9216, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, + 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9375, 9600, 9600, 9600, 9600, 9600, 9600, + 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, + 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9600, 9720, 9720, 9720, 9720, 9720, 9720, + 9720, 9720, 9720, 9720, 9720, 9720, 9720, 9720, 9720, 9720, 9720, 9720, 10000, 10000, 10000, 10000, + 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, + 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, + 10000, 10000, 10000, 10000, 10000, 10000, 10125, 10125, 10125, 10125, 10125, 10125, 10125, 10125, 10125, 10125, + 10125, 10125, 10125, 10125, 10125, 10125, 10125, 10125, 10125, 10240, 10240, 10240, 10240, 10240, 10240, 10240, + 10240, 10240, 10240, 10240, 10240, 10240, 10240, 10240, 10240, 10240, 10240, 10368, 10368, 10368, 10368, 10368, + 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10368, 10800, 10800, + 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, + 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, + 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, + 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10800, 10935, + 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, 10935, + 10935, 10935, 10935, 10935, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, + 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, + 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, 11250, + 11250, 11250, 11250, 11250, 11250, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, + 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, + 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11520, 11664, 11664, + 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, 11664, + 11664, 11664, 11664, 11664, 11664, 11664, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, + 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, + 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, + 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12150, 12150, 12150, 12150, 12150, 12150, + 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, 12150, + 12150, 12150, 12150, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, + 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12288, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, + 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, + 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12500, 12800, 12800, 12800, 12800, 12800, 12800, + 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, + 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, + 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12800, 12960, 12960, 12960, 12960, 12960, 12960, + 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, 12960, + 12960, 12960, 12960, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, + 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13122, 13500, 13500, + 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, + 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, + 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, + 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13500, 13824, 13824, 13824, + 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, + 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, + 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, 13824, + 13824, 13824, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, 14400, + 14400, 14400, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, + 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, 14580, + 14580, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, + 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, + 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, + 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15000, + 15000, 15000, 15000, 15000, 15000, 15000, 15000, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, + 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, + 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, + 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, + 15360, 15360, 15360, 15360, 15360, 15360, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, + 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, 15552, + 15552, 15552, 15552, 15552, 15552, 15552, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, 15625, + 15625, 15625, 15625, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, 16000, + 16000, 16000, 16000, 16000, 16200, 16200, 16200, 16200, 16200, 16200, 16200, 16200, 16200, 16200, 16200, 16200, + 16200, 16200, 16200, 16200, 16200, 16200, 16200, 16200, 16200, 16200, 16200, 16200, 16200, 16200, 16200, 16200, + 16200, 16200, 16200, 16200, 16200, 16200, 16200, 16200, 16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, + 16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, + 16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, 16875, 16875, 16875, 16875, 16875, 16875, 16875, + 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, + 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, + 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, + 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, + 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, 16875, + 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, + 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, + 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, + 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, + 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17280, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, + 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, + 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, 17496, + 17496, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, + 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, + 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, + 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, + 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, + 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18000, 18225, 18225, 18225, + 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, + 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18225, + 18225, 18225, 18225, 18225, 18225, 18225, 18225, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, + 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, + 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18750, 18750, 18750, 18750, + 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, + 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, + 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, + 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 18750, 19200, 19200, 19200, 19200, 19200, 19200, 19200, + 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, + 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, + 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, + 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, + 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19200, 19440, 19440, + 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, + 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, + 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19440, 19683, 19683, 19683, + 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, + 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, + 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 19683, 20000, 20000, 20000, + 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, + 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, + 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, + 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20000, 20250, 20250, 20250, 20250, 20250, 20250, + 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, + 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, + 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20250, 20480, 20480, 20480, 20480, + 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, + 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, + 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20480, 20736, 20736, 20736, 20736, 20736, + 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, + 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, + 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 20736, 21600, + 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, + 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, + 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, + 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, + 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, + 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, + 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, + 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, + 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, + 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, + 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, 21600, + 21600, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, + 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, + 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, + 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 21870, 22500, 22500, 22500, 22500, 22500, + 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, + 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, + 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, + 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, + 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, + 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, + 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, + 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, 22500, + 22500, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, + 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, + 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, + 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, + 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, + 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, + 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, + 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23040, 23328, 23328, 23328, 23328, 23328, 23328, + 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, + 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, + 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, + 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 23328, 24000, 24000, 24000, 24000, 24000, + 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, + 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, + 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, + 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, + 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, + 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, + 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, + 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, + 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, 24000, + 24000, 24000, 24000, 24000, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, + 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, + 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, + 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, + 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24300, 24576, 24576, 24576, + 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, + 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, + 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, + 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, 24576, + 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, + 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, + 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, + 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, + 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, + 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, + 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, + 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, + 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, + 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, + 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, + 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, + 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, + 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, + 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, + 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, 25600, + 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, + 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, + 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, + 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, + 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, 25920, + 25920, 25920, 25920, 25920, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, + 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, + 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, + 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, + 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, + 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 26244, 27000, 27000, 27000, + 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, + 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, + 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, + 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, + 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, + 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, + 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, + 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, + 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, + 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, + 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, + 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, + 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27000, 27648, + 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, + 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, + 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, + 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, + 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, + 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, + 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, + 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, + 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, + 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, + 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, + 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, 27648, + 27648, 27648, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, + 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, + 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, + 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, + 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, + 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, + 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, + 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, + 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, + 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28125, 28800, 28800, 28800, 28800, + 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, + 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, + 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, + 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, + 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, + 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, + 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, + 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, + 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, + 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, + 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, + 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, + 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, + 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, 28800, + 28800, 28800, 28800, 28800, 28800, 28800, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, + 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, + 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, + 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, + 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, + 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, + 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, + 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, + 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 29160, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, + 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30375, + 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, + 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, + 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, + 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, + 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, + 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, + 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, + 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, + 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, + 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, + 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, 30375, + 30375, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, + 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, + 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, + 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, + 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, + 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, + 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, + 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, + 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, + 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, + 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, + 30720, 30720, 30720, 30720, 30720, 30720, 30720, 30720, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, + 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, + 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, + 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, + 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, + 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, + 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, + 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, + 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, + 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, + 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, + 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, + 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, + 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, + 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, 31104, + 31104, 31104, 31104, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, + 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, + 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, + 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, + 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, + 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, + 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 31250, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, + 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000}}, +}; + + +bool reduced_classical_pl_known(size_t N) { + return CLASSICAL_PLS.find(N) != CLASSICAL_PLS.end(); +} + + +const pl_type& reduced_classical_pl(size_t N) { + ASSERT(N > 0); + + static CacheT cache; + if (cache.contains(N)) { + return cache[N]; + } + + auto pl_half = CLASSICAL_PLS.find(N); + if (pl_half == CLASSICAL_PLS.end()) { + throw BadValue("reduced_classical_pl: unknown N=" + std::to_string(N), Here()); + } + + ASSERT(pl_half->second.size() == N); + + pl_type pl(N * 2); + + auto p = pl_half->second.begin(); + for (size_t i = 0, j = 2 * N - 1; i < N; ++i, --j) { + pl[i] = pl[j] = *p++; + } + + return (cache[N] = std::move(pl)); +} + + +} // namespace eckit::geo::util diff --git a/src/eckit/geo/util/reduced_octahedral_pl.cc b/src/eckit/geo/util/reduced_octahedral_pl.cc new file mode 100644 index 000000000..864aa857e --- /dev/null +++ b/src/eckit/geo/util/reduced_octahedral_pl.cc @@ -0,0 +1,38 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Cache.h" +#include "eckit/geo/util.h" + + +namespace eckit::geo::util { + + +const pl_type& reduced_octahedral_pl(size_t N) { + static CacheT cache; + if (cache.contains(N)) { + return cache[N]; + } + + pl_type pl(N * 2); + + pl_type::value_type p = 20; + for (size_t i = 0, j = 2 * N - 1; i < N; ++i, --j) { + pl[i] = pl[j] = p; + p += 4; + } + + return (cache[N] = std::move(pl)); +} + + +} // namespace eckit::geo::util diff --git a/src/eckit/geo/util/sincos.h b/src/eckit/geo/util/sincos.h new file mode 100644 index 000000000..ec7cc9eb8 --- /dev/null +++ b/src/eckit/geo/util/sincos.h @@ -0,0 +1,30 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + + +namespace eckit::geo::util { + + +struct sincos_t final : std::array { + explicit sincos_t(value_type r) : array{std::sin(r), std::cos(r)} {} + + const value_type& sin = array::operator[](0); + const value_type& cos = array::operator[](1); +}; + + +} // namespace eckit::geo::util diff --git a/src/eckit/geometry/CoordinateHelpers.cc b/src/eckit/geometry/CoordinateHelpers.cc index 48ab93117..d608e4344 100644 --- a/src/eckit/geometry/CoordinateHelpers.cc +++ b/src/eckit/geometry/CoordinateHelpers.cc @@ -5,6 +5,7 @@ * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. */ +#include #include #include @@ -16,7 +17,22 @@ namespace eckit::geometry { //---------------------------------------------------------------------------------------------------------------------- +inline double modulo(const double a, const double b) { + return a - b * std::floor(a / b); +} + double normalise_angle(double a, const double minimum) { + constexpr double treshold = 4. * 360.; + const double diff = a - minimum; + if (std::abs(diff) > treshold) { + // This formula is not bit-dentical with original below, + // but is faster for very large values of 'a'. + // The treshold tries to capture values of 'a' which need to + // stay bit-identical. + return minimum + modulo(diff, 360.); + } + + // The original function while (a < minimum) { a += 360.; } diff --git a/src/eckit/io/AIOHandle.cc b/src/eckit/io/AIOHandle.cc index 0d957d1e5..96c441e76 100644 --- a/src/eckit/io/AIOHandle.cc +++ b/src/eckit/io/AIOHandle.cc @@ -11,7 +11,7 @@ #include "eckit/eckit.h" #include -#include +#include #include #include diff --git a/src/eckit/io/EasyCURL.cc b/src/eckit/io/EasyCURL.cc index 73858f087..f9e3962c3 100644 --- a/src/eckit/io/EasyCURL.cc +++ b/src/eckit/io/EasyCURL.cc @@ -8,6 +8,11 @@ * does it submit to any jurisdiction. */ +// Disable warnings: warning #550-D: variable "__d0" was set but never used [set_but_not_used] in FD_ZERO(&r); +#if defined(__NVCOMPILER) +#pragma diag_suppress 550 +#endif + #include "eckit/io/EasyCURL.h" #include diff --git a/src/eckit/io/MultiHandle.cc b/src/eckit/io/MultiHandle.cc index 659fe5396..d634feef7 100644 --- a/src/eckit/io/MultiHandle.cc +++ b/src/eckit/io/MultiHandle.cc @@ -149,11 +149,13 @@ long MultiHandle::read1(char* buffer, long length) { } long n = (*current_)->read(buffer, length); - if (n <= 0) { + if (n < length) { (*current_)->close(); current_++; openCurrent(); - return read1(buffer, length); + if (n <= 0) { + return read1(buffer, length); + } } return n; } diff --git a/src/eckit/io/Offset.cc b/src/eckit/io/Offset.cc index e707cd42e..a5bc5112e 100644 --- a/src/eckit/io/Offset.cc +++ b/src/eckit/io/Offset.cc @@ -86,6 +86,10 @@ Stream& operator>>(Stream& s, Offset& x) { return s; } +void Offset::dump(DumpLoad& a) const { + a.dump(value_); +} + void Offset::load(DumpLoad& a) { a.load(value_); } diff --git a/src/eckit/io/PooledFile.cc b/src/eckit/io/PooledFile.cc index 2eab33485..441a9e7d2 100644 --- a/src/eckit/io/PooledFile.cc +++ b/src/eckit/io/PooledFile.cc @@ -8,25 +8,48 @@ * does it submit to any jurisdiction. */ +#include "eckit/io/PooledFile.h" + #include +#include #include +#include #include -#include +#include +#include #include "eckit/config/LibEcKit.h" #include "eckit/config/Resource.h" #include "eckit/exception/Exceptions.h" #include "eckit/filesystem/PathName.h" #include "eckit/io/Buffer.h" -#include "eckit/io/PooledFile.h" #include "eckit/log/Bytes.h" - namespace eckit { - class PoolFileEntry; +} + +namespace { + +class Pool { + +private: + Pool() {} + std::map> filePool_; + std::mutex filePoolMutex_; + +public: + static Pool& instance() { + static Pool pool; + return pool; + } + eckit::PoolFileEntry* get(const eckit::PathName& name); + void erase(const eckit::PathName& name); +}; + +} -static thread_local std::map> pool_; +namespace eckit { struct PoolFileEntryStatus { @@ -73,12 +96,12 @@ class PoolFileEntry { void remove(const PooledFile* file) { auto s = statuses_.find(file); - if (s != statuses_.end()) { - statuses_.erase(s); - } + ASSERT(s != statuses_.end()); + + statuses_.erase(s); if (statuses_.size() == 0) { doClose(); - pool_.erase(name_); + Pool::instance().erase(name_); // No code after !!! } } @@ -113,10 +136,10 @@ class PoolFileEntry { void close(const PooledFile* file) { auto s = statuses_.find(file); - if (s != statuses_.end()) { - ASSERT(s->second.opened_); - s->second.opened_ = false; - } + ASSERT(s != statuses_.end()); + + ASSERT(s->second.opened_); + s->second.opened_ = false; } int fileno(const PooledFile* file) const { @@ -193,14 +216,8 @@ class PoolFileEntry { PooledFile::PooledFile(const PathName& name) : - name_(name), entry_(nullptr) { - auto j = pool_.find(name); - if (j == pool_.end()) { - pool_.emplace(std::make_pair(name, std::unique_ptr(new PoolFileEntry(name)))); - j = pool_.find(name); - } + name_(name), entry_(Pool::instance().get(name)) { - entry_ = (*j).second.get(); entry_->add(this); } @@ -262,3 +279,21 @@ PooledFileError::PooledFileError(const std::string& file, const std::string& msg FileError(msg + " : error on pooled file " + file, loc) {} } // namespace eckit + +namespace { + +eckit::PoolFileEntry* Pool::get(const eckit::PathName& name) { + std::lock_guard lock(filePoolMutex_); + auto j = filePool_.find(name); + if (j == filePool_.end()) { + filePool_.emplace(name, new eckit::PoolFileEntry(name)); + j = filePool_.find(name); + } + return (*j).second.get(); +} +void Pool::erase(const eckit::PathName& name) { + std::lock_guard lock(filePoolMutex_); + filePool_.erase(name); +} + +} \ No newline at end of file diff --git a/src/eckit/io/Select.cc b/src/eckit/io/Select.cc index 03677c86d..d88909079 100644 --- a/src/eckit/io/Select.cc +++ b/src/eckit/io/Select.cc @@ -8,6 +8,10 @@ * does it submit to any jurisdiction. */ +// Disable warnings: warning #550-D: variable "__d0" was set but never used [set_but_not_used] in FD_ZERO(&r); +#if defined(__NVCOMPILER) +#pragma diag_suppress 550 +#endif #include #include diff --git a/src/eckit/io/SharedHandle.h b/src/eckit/io/SharedHandle.h index e21b0d759..8f58e7c90 100644 --- a/src/eckit/io/SharedHandle.h +++ b/src/eckit/io/SharedHandle.h @@ -66,6 +66,7 @@ class SharedHandle : public DataHandle { DataHandle* clone() const override; + using DataHandle::saveInto; Length saveInto(DataHandle& other, TransferWatcher& watcher) override; std::string name() const override; diff --git a/src/eckit/io/StatsHandle.h b/src/eckit/io/StatsHandle.h index 435082d88..2de31e8be 100644 --- a/src/eckit/io/StatsHandle.h +++ b/src/eckit/io/StatsHandle.h @@ -67,6 +67,7 @@ class StatsHandle : public DataHandle, public HandleHolder { DataHandle* clone() const override; + using DataHandle::saveInto; Length saveInto(DataHandle& other, TransferWatcher& watcher) override; std::string name() const override; diff --git a/src/eckit/io/StdFile.cc b/src/eckit/io/StdFile.cc index 1195b5c96..96a4eb250 100644 --- a/src/eckit/io/StdFile.cc +++ b/src/eckit/io/StdFile.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include "eckit/exception/Exceptions.h" #include "eckit/filesystem/PathName.h" diff --git a/src/eckit/io/StdFile.h b/src/eckit/io/StdFile.h index 5617f0275..040015e7e 100644 --- a/src/eckit/io/StdFile.h +++ b/src/eckit/io/StdFile.h @@ -15,7 +15,7 @@ #ifndef eckit_io_StdFile_h #define eckit_io_StdFile_h -#include +#include #include "eckit/filesystem/PathName.h" #include "eckit/memory/NonCopyable.h" diff --git a/src/eckit/io/StdPipe.cc b/src/eckit/io/StdPipe.cc index fc160227f..619b12b01 100644 --- a/src/eckit/io/StdPipe.cc +++ b/src/eckit/io/StdPipe.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include #include "eckit/exception/Exceptions.h" diff --git a/src/eckit/io/StdPipe.h b/src/eckit/io/StdPipe.h index afa3fd161..bab343a27 100644 --- a/src/eckit/io/StdPipe.h +++ b/src/eckit/io/StdPipe.h @@ -15,7 +15,7 @@ #ifndef eckit_io_StdPipe_h #define eckit_io_StdPipe_h -#include +#include #include #include "eckit/io/AutoCloser.h" diff --git a/src/eckit/io/StdioBuf.h b/src/eckit/io/StdioBuf.h index 65503c2dc..1068d38f5 100644 --- a/src/eckit/io/StdioBuf.h +++ b/src/eckit/io/StdioBuf.h @@ -14,7 +14,7 @@ #ifndef eckit_StdioBuf_h #define eckit_StdioBuf_h -#include +#include #include "eckit/eckit.h" diff --git a/src/eckit/io/cluster/ClusterNodes.cc b/src/eckit/io/cluster/ClusterNodes.cc index cb88dc461..a32e31d76 100644 --- a/src/eckit/io/cluster/ClusterNodes.cc +++ b/src/eckit/io/cluster/ClusterNodes.cc @@ -171,7 +171,7 @@ class ClusterNodeEntry { const char* host() const { return host_; } void attributes(const std::set& attrs) { - ASSERT(attrs.size() >= 0 && attrs.size() <= MAX_NODE_ATTRIBUTES); + ASSERT(attrs.size() <= MAX_NODE_ATTRIBUTES); zero(attributes_); nattrs_ = 0; for (const auto& a : attrs) { diff --git a/src/eckit/linalg/CMakeLists.txt b/src/eckit/linalg/CMakeLists.txt index ec3160a5b..ce062036e 100644 --- a/src/eckit/linalg/CMakeLists.txt +++ b/src/eckit/linalg/CMakeLists.txt @@ -49,7 +49,6 @@ if( eckit_HAVE_EIGEN ) dense/LinearAlgebraEigen.h sparse/LinearAlgebraEigen.cc sparse/LinearAlgebraEigen.h ) - list( APPEND eckit_la_pincludes "${EIGEN3_INCLUDE_DIRS}" ) endif() if( eckit_HAVE_LAPACK ) @@ -90,6 +89,15 @@ ecbuild_add_library( TARGET eckit_linalg TYPE SHARED PUBLIC_LIBS eckit PRIVATE_LIBS ${eckit_la_plibs} ) +if( eckit_HAVE_EIGEN ) + if(NOT EIGEN3_INCLUDE_DIRS AND TARGET Eigen3::Eigen) + get_target_property(EIGEN3_INCLUDE_DIRS Eigen3::Eigen INTERFACE_INCLUDE_DIRECTORIES) + endif() + + # Add include directories with "SYSTEM" to avoid warnings from within Eigen headers + target_include_directories( eckit_linalg SYSTEM PRIVATE ${EIGEN3_INCLUDE_DIRS} ) +endif() + if( CUDA_FOUND ) set( CUDA_LINK_LIBRARIES_KEYWORD PRIVATE ) cuda_add_cublas_to_target( eckit_linalg ) diff --git a/src/eckit/linalg/SparseMatrix.cc b/src/eckit/linalg/SparseMatrix.cc index 7f41d39ce..7c1663ed4 100644 --- a/src/eckit/linalg/SparseMatrix.cc +++ b/src/eckit/linalg/SparseMatrix.cc @@ -12,30 +12,30 @@ #include "eckit/linalg/SparseMatrix.h" #include +#include #include -#include -#include +#include +#include #include "eckit/eckit.h" // for endianness #include "eckit/config/LibEcKit.h" #include "eckit/exception/Exceptions.h" -#include "eckit/filesystem/PathName.h" #include "eckit/io/AutoCloser.h" -#include "eckit/io/BufferedHandle.h" #include "eckit/io/MemoryHandle.h" #include "eckit/log/Bytes.h" +#include "eckit/log/Log.h" #include "eckit/memory/MemoryBuffer.h" #include "eckit/serialisation/FileStream.h" #include "eckit/serialisation/Stream.h" namespace eckit::linalg { -#if eckit_LITTLE_ENDIAN -static const bool littleEndian = true; -#else -static const bool littleEndian = false; -#endif +// Extend range of Index for unsigned compression indices (should only be positive), preserving binary compatibility +static_assert(sizeof(Index) == sizeof(SparseMatrix::UIndex), "sizeof(sizeof(Index) == SparseMatrix::UIndex)"); + +static constexpr bool littleEndian = eckit_LITTLE_ENDIAN != 0; + //---------------------------------------------------------------------------------------------------------------------- @@ -43,11 +43,9 @@ namespace detail { class StandardAllocator : public SparseMatrix::Allocator { public: - StandardAllocator() : - membuff_(0) {} - - virtual SparseMatrix::Layout allocate(SparseMatrix::Shape& shape) { + StandardAllocator() : membuff_(0) {} + SparseMatrix::Layout allocate(SparseMatrix::Shape& shape) override { if (shape.allocSize() > membuff_.size()) { membuff_.resize(shape.allocSize()); } @@ -57,162 +55,173 @@ class StandardAllocator : public SparseMatrix::Allocator { char* addr = membuff_; p.data_ = reinterpret_cast(addr); - p.outer_ = reinterpret_cast(addr + shape.sizeofData()); + p.outer_ = reinterpret_cast(addr + shape.sizeofData()); p.inner_ = reinterpret_cast(addr + shape.sizeofData() + shape.sizeofOuter()); return p; } - virtual void deallocate(SparseMatrix::Layout p, SparseMatrix::Shape) {} - - virtual bool inSharedMemory() const { return false; } - - virtual void print(std::ostream& out) const { out << "StandardAllocator[" << Bytes(membuff_.size()) << "]"; } + void deallocate(SparseMatrix::Layout p, SparseMatrix::Shape) override {} + bool inSharedMemory() const override { return false; } + void print(std::ostream& out) const override { + out << "StandardAllocator[" << Bytes{static_cast(membuff_.size())} << "]"; + } - eckit::MemoryBuffer membuff_; + MemoryBuffer membuff_; }; + class BufferAllocator : public SparseMatrix::Allocator { public: - BufferAllocator(const MemoryBuffer& buffer) : - buffer_(buffer, buffer.size()) {} - - virtual eckit::linalg::SparseMatrix::Layout allocate(eckit::linalg::SparseMatrix::Shape& shape) { - - using namespace eckit::linalg; + BufferAllocator(const MemoryBuffer& buffer) : buffer_(buffer, buffer.size()) {} + SparseMatrix::Layout allocate(SparseMatrix::Shape& shape) override { SparseMatrix::Layout layout; - eckit::linalg::SparseMatrix::load(buffer_.data(), buffer_.size(), layout, shape); + SparseMatrix::load(buffer_.data(), buffer_.size(), layout, shape); return layout; } - virtual void deallocate(eckit::linalg::SparseMatrix::Layout, eckit::linalg::SparseMatrix::Shape) {} - - virtual bool inSharedMemory() const { return false; } - - virtual void print(std::ostream& out) const { out << "BufferAllocator[" << Bytes(buffer_.size()) << "]"; } + void deallocate(SparseMatrix::Layout, SparseMatrix::Shape) override {} + bool inSharedMemory() const override { return false; } + void print(std::ostream& out) const override { + out << "BufferAllocator[" << Bytes{static_cast(buffer_.size())} << "]"; + } MemoryBuffer buffer_; }; + } // namespace detail //---------------------------------------------------------------------------------------------------------------------- -SparseMatrix::SparseMatrix(Allocator* alloc) { - owner_.reset(alloc ? alloc : new detail::StandardAllocator()); +void SparseMatrix::Shape::print(std::ostream& os) const { + os << "Shape[" + << "nnz=" << size_ << "," + << "rows=" << rows_ << "," + << "cols=" << cols_ << "]"; +} + + +SparseMatrix::SparseMatrix(Allocator* alloc) : owner_(alloc != nullptr ? alloc : new detail::StandardAllocator) { spm_ = owner_->allocate(shape_); } -SparseMatrix::SparseMatrix(Size rows, Size cols, Allocator* alloc) { - owner_.reset(alloc ? alloc : new detail::StandardAllocator()); + +SparseMatrix::SparseMatrix(Size rows, Size cols, Allocator* alloc) : + owner_(alloc != nullptr ? alloc : new detail::StandardAllocator) { reserve(rows, cols, 1); } + SparseMatrix::SparseMatrix(Size rows, Size cols, const std::vector& triplets) : - owner_(new detail::StandardAllocator()) { + owner_(new detail::StandardAllocator) { - // Count number of non-zeros - Size nnz{0}; - for (std::vector::const_iterator it = triplets.begin(); it != triplets.end(); ++it) { - if (it->nonZero()) { - ++nnz; - } + // Count number of non-zeros, allocate memory 1 triplet per non-zero + Size nnz = std::count_if(triplets.begin(), triplets.end(), [](const auto& tri) { return tri.nonZero(); }); + + if (auto max = static_cast(std::numeric_limits::max()); max < nnz) { + throw OutOfRange("SparseMatrix::SparseMatrix: too many non-zero entries, nnz=" + std::to_string(nnz) + + ", max=" + std::to_string(max), + Here()); } - reserve(rows, cols, nnz); // allocate memory 1 triplet per non-zero + reserve(rows, cols, nnz); Size pos = 0; Size row = 0; - spm_.outer_[0] = 0; /* first entry is always zero */ + spm_.outer_[0] = 0; /* first entry (base) is always zero */ // Build vectors of inner indices and values, update outer index per row - for (std::vector::const_iterator it = triplets.begin(); it != triplets.end(); ++it) { - - if (it->nonZero()) { + for (const auto& tri : triplets) { + if (tri.nonZero()) { // triplets are ordered by rows - ASSERT(it->row() >= row); - ASSERT(it->row() < shape_.rows_); - // ASSERT( it->col() >= 0 ); // useless comparison with unsigned int - ASSERT(it->col() < shape_.cols_); + ASSERT(tri.row() >= row); + ASSERT(tri.row() < shape_.rows_); + ASSERT(tri.col() < shape_.cols_); // start a new row - while (it->row() > row) { - spm_.outer_[++row] = Index(pos); + while (tri.row() > row) { + spm_.outer_[++row] = static_cast(pos); } - spm_.inner_[pos] = Index(it->col()); - spm_.data_[pos] = it->value(); + spm_.inner_[pos] = static_cast(tri.col()); + spm_.data_[pos] = tri.value(); ++pos; } } while (row < shape_.rows_) { - spm_.outer_[++row] = Index(pos); + spm_.outer_[++row] = static_cast(pos); } - ASSERT(Size(spm_.outer_[shape_.outerSize() - 1]) == nonZeros()); /* last entry is always the nnz */ + ASSERT(static_cast(spm_.outer_[shape_.outerSize() - 1]) == nonZeros()); } -SparseMatrix::SparseMatrix(Stream& s) { - owner_.reset(new detail::StandardAllocator()); +SparseMatrix::SparseMatrix(Stream& s) : owner_(new detail::StandardAllocator) { decode(s); } -SparseMatrix::SparseMatrix(const MemoryBuffer& buffer) { - owner_.reset(new detail::BufferAllocator(buffer)); + +SparseMatrix::SparseMatrix(const MemoryBuffer& buffer) : owner_(new detail::BufferAllocator(buffer)) { spm_ = owner_->allocate(shape_); } -SparseMatrix::SparseMatrix(const SparseMatrix& other) { - - owner_.reset(new detail::StandardAllocator()); +SparseMatrix::SparseMatrix(const SparseMatrix& other) : owner_(new detail::StandardAllocator) { if (!other.empty()) { // in case we copy an other that was constructed empty reserve(other.rows(), other.cols(), other.nonZeros()); - ::memcpy(spm_.data_, other.spm_.data_, shape_.sizeofData()); - ::memcpy(spm_.outer_, other.spm_.outer_, shape_.sizeofOuter()); - ::memcpy(spm_.inner_, other.spm_.inner_, shape_.sizeofInner()); + std::memcpy(spm_.data_, other.spm_.data_, shape_.sizeofData()); + std::memcpy(spm_.outer_, other.spm_.outer_, shape_.sizeofOuter()); + std::memcpy(spm_.inner_, other.spm_.inner_, shape_.sizeofInner()); } } -SparseMatrix::SparseMatrix(SparseMatrix&& other) : - SparseMatrix() { + +SparseMatrix::SparseMatrix(SparseMatrix&& other) { swap(other); } + SparseMatrix& SparseMatrix::operator=(const SparseMatrix& other) { SparseMatrix copy(other); swap(copy); return *this; } + +SparseMatrix& SparseMatrix::operator=(SparseMatrix&& other) { + swap(other); + return *this; +} + + SparseMatrix::~SparseMatrix() { reset(); } -void SparseMatrix::reset() { - owner_->deallocate(spm_, shape_); +void SparseMatrix::reset() { + if (owner_) { + owner_->deallocate(spm_, shape_); + } spm_.reset(); shape_.reset(); } -// variables into this method must be by value void SparseMatrix::reserve(Size rows, Size cols, Size nnz) { - ASSERT(nnz > 0); ASSERT(nnz <= rows * cols); - ASSERT(rows > 0 && cols > 0); /* rows and columns must have some size */ + ASSERT(rows > 0 && cols > 0); reset(); @@ -224,19 +233,20 @@ void SparseMatrix::reserve(Size rows, Size cols, Size nnz) { } -void SparseMatrix::save(const eckit::PathName& path) const { +void SparseMatrix::save(const PathName& path) const { FileStream s(path, "w"); auto c = closer(s); encode(s); } -void SparseMatrix::load(const eckit::PathName& path) { +void SparseMatrix::load(const PathName& path) { FileStream s(path, "r"); auto c = closer(s); decode(s); } + struct SPMInfo { size_t size_; ///< non-zeros size_t rows_; ///< rows @@ -246,10 +256,11 @@ struct SPMInfo { ptrdiff_t inner_; }; + void SparseMatrix::load(const void* buffer, size_t bufferSize, Layout& layout, Shape& shape) { - const char* b = static_cast(buffer); + const auto* b = static_cast(buffer); - eckit::MemoryHandle mh(buffer, bufferSize); + MemoryHandle mh(buffer, bufferSize); mh.openForRead(); struct SPMInfo info; @@ -270,10 +281,10 @@ void SparseMatrix::load(const void* buffer, size_t bufferSize, Layout& layout, S ASSERT(bufferSize >= sizeof(SPMInfo) + shape.sizeofData() + shape.sizeofOuter() + shape.sizeofInner()); - char* addr = const_cast(b); + auto* addr = const_cast(b); layout.data_ = reinterpret_cast(addr + info.data_); - layout.outer_ = reinterpret_cast(addr + info.outer_); + layout.outer_ = reinterpret_cast(addr + info.outer_); layout.inner_ = reinterpret_cast(addr + info.inner_); // check offsets don't segfault @@ -283,12 +294,13 @@ void SparseMatrix::load(const void* buffer, size_t bufferSize, Layout& layout, S ASSERT(info.inner_ + shape.sizeofInner() <= bufferSize); } + void SparseMatrix::dump(MemoryBuffer& buffer) const { SparseMatrix::dump(buffer.data(), buffer.size()); } -void SparseMatrix::dump(void* buffer, size_t size) const { +void SparseMatrix::dump(void* buffer, size_t size) const { size_t minimum = sizeof(SPMInfo) + shape_.sizeofData() + shape_.sizeofOuter() + shape_.sizeofInner(); ASSERT(size >= minimum); @@ -313,36 +325,50 @@ void SparseMatrix::dump(void* buffer, size_t size) const { mh.write(&info, sizeof(SPMInfo)); - ASSERT(mh.write(spm_.data_, shape_.sizeofData()) == long(shape_.sizeofData())); - ASSERT(mh.write(spm_.outer_, shape_.sizeofOuter()) == long(shape_.sizeofOuter())); - ASSERT(mh.write(spm_.inner_, shape_.sizeofInner()) == long(shape_.sizeofInner())); + ASSERT(mh.write(spm_.data_, shape_.sizeofData()) == static_cast(shape_.sizeofData())); + ASSERT(mh.write(spm_.outer_, shape_.sizeofOuter()) == static_cast(shape_.sizeofOuter())); + ASSERT(mh.write(spm_.inner_, shape_.sizeofInner()) == static_cast(shape_.sizeofInner())); } -void SparseMatrix::swap(SparseMatrix& other) { +void SparseMatrix::swap(SparseMatrix& other) { std::swap(spm_, other.spm_); std::swap(shape_, other.shape_); owner_.swap(other.owner_); } + +const Index* SparseMatrix::outer() const { + if (auto max = static_cast(std::numeric_limits::max()); max < nonZeros()) { + throw OutOfRange("SparseMatrix::outer: too many non-zero entries, nnz=" + std::to_string(nonZeros()) + + ", max=" + std::to_string(max) + " (for Index-typed compatibility)", + Here()); + } + + return reinterpret_cast(spm_.outer_); +} + + void SparseMatrix::cols(Size cols) { ASSERT(cols > 0); shape_.cols_ = cols; } + size_t SparseMatrix::footprint() const { return sizeof(*this) + shape_.allocSize(); } + bool SparseMatrix::inSharedMemory() const { - ASSERT(owner_.get()); + ASSERT(owner_); return owner_->inSharedMemory(); } + void SparseMatrix::dump(std::ostream& os) const { for (Size i = 0; i < rows(); ++i) { - const_iterator itr = begin(i); const_iterator iend = end(i); @@ -358,29 +384,30 @@ void SparseMatrix::dump(std::ostream& os) const { } } + void SparseMatrix::print(std::ostream& os) const { os << "SparseMatrix[" << shape_ << "," << *owner_ << "]"; } -SparseMatrix& SparseMatrix::setIdentity(Size rows, Size cols) { +SparseMatrix& SparseMatrix::setIdentity(Size rows, Size cols) { ASSERT(rows > 0 && cols > 0); - Size nnz = std::min(rows, cols); + auto nnz = std::min(rows, cols); reserve(rows, cols, nnz); for (Size i = 0; i < nnz; ++i) { - spm_.outer_[i] = Index(i); - spm_.inner_[i] = Index(i); + spm_.outer_[i] = static_cast(i); + spm_.inner_[i] = static_cast(i); } for (Size i = nnz; i <= shape_.rows_; ++i) { - spm_.outer_[i] = Index(nnz); + spm_.outer_[i] = static_cast(nnz); } for (Size i = 0; i < shape_.size_; ++i) { - spm_.data_[i] = Scalar(1); + spm_.data_[i] = static_cast(1); } return *this; @@ -388,16 +415,16 @@ SparseMatrix& SparseMatrix::setIdentity(Size rows, Size cols) { SparseMatrix& SparseMatrix::transpose() { - /// @note Can SparseMatrix::transpose() be done more efficiently? /// We are building another matrix and then swapping std::vector triplets; triplets.reserve(nonZeros()); + for (Size r = 0; r < shape_.rows_; ++r) { - for (Index c = spm_.outer_[r]; c < spm_.outer_[r + 1]; ++c) { + for (auto c = spm_.outer_[r]; c < spm_.outer_[r + 1]; ++c) { ASSERT(spm_.inner_[c] >= 0); - triplets.push_back(Triplet(Size(spm_.inner_[c]), r, spm_.data_[c])); + triplets.emplace_back(static_cast(spm_.inner_[c]), r, spm_.data_[c]); } } @@ -410,41 +437,33 @@ SparseMatrix& SparseMatrix::transpose() { return *this; } + SparseMatrix SparseMatrix::rowReduction(const std::vector& p) const { ASSERT(p.size() <= rows()); std::vector triplets; for (size_t newrow = 0; newrow < p.size(); ++newrow) { - size_t row = p[newrow]; - const_iterator itr = begin(row); - const_iterator iend = end(row); - - if (itr == iend) { - continue; - } - - for (; itr != iend; ++itr) { - triplets.push_back(Triplet(newrow, itr.col(), *itr)); + for (auto itr = begin(row), iend = end(row); itr != iend; ++itr) { + triplets.emplace_back(newrow, itr.col(), *itr); } } - return SparseMatrix(p.size(), cols(), triplets); + return {p.size(), cols(), triplets}; } -SparseMatrix& SparseMatrix::prune(linalg::Scalar val) { - +SparseMatrix& SparseMatrix::prune(Scalar val) { std::vector v; std::vector inner; Size nnz = 0; for (Size r = 0; r < shape_.rows_; ++r) { - const Index start = spm_.outer_[r]; - spm_.outer_[r] = Index(nnz); - for (Index c = start; c < spm_.outer_[r + 1]; ++c) { + const auto start = spm_.outer_[r]; + spm_.outer_[r] = static_cast(nnz); + for (auto c = start; c < spm_.outer_[r + 1]; ++c) { if (spm_.data_[c] != val) { v.push_back(spm_.data_[c]); inner.push_back(spm_.inner_[c]); @@ -452,28 +471,28 @@ SparseMatrix& SparseMatrix::prune(linalg::Scalar val) { } } } - spm_.outer_[shape_.rows_] = Index(nnz); + spm_.outer_[shape_.rows_] = static_cast(nnz); SparseMatrix tmp; tmp.reserve(shape_.rows_, shape_.cols_, nnz); - ::memcpy(tmp.spm_.data_, v.data(), nnz * sizeof(Scalar)); - ::memcpy(tmp.spm_.outer_, spm_.outer_, shape_.outerSize() * sizeof(Index)); - ::memcpy(tmp.spm_.inner_, inner.data(), nnz * sizeof(Index)); + std::memcpy(tmp.spm_.data_, v.data(), nnz * sizeof(Scalar)); + std::memcpy(tmp.spm_.outer_, spm_.outer_, shape_.outerSize() * sizeof(UIndex)); + std::memcpy(tmp.spm_.inner_, inner.data(), nnz * sizeof(Index)); swap(tmp); return *this; } + const SparseMatrix::Allocator& SparseMatrix::owner() const { - ASSERT(owner_.get()); - return *(owner_.get()); + ASSERT(owner_); + return *owner_; } void SparseMatrix::encode(Stream& s) const { - s << shape_.rows_; s << shape_.cols_; s << shape_.size_; @@ -487,41 +506,40 @@ void SparseMatrix::encode(Stream& s) const { << " rows " << rows() << " cols " << cols() << " nnz " << nonZeros() << " footprint " << footprint() << std::endl; - s.writeLargeBlob(spm_.outer_, shape_.outerSize() * sizeof(Index)); + s.writeLargeBlob(spm_.outer_, shape_.outerSize() * sizeof(UIndex)); s.writeLargeBlob(spm_.inner_, shape_.innerSize() * sizeof(Index)); s.writeLargeBlob(spm_.data_, shape_.dataSize() * sizeof(Scalar)); } void SparseMatrix::decode(Stream& s) { - - Size rows; - Size cols; - Size nnz; + Size rows = 0; + Size cols = 0; + Size nnz = 0; s >> rows; s >> cols; s >> nnz; - bool little_endian; + bool little_endian = true; s >> little_endian; ASSERT(littleEndian == little_endian); - size_t index_size; + size_t index_size = 0; s >> index_size; ASSERT(index_size == sizeof(Index)); - size_t scalar_size; + size_t scalar_size = 0; s >> scalar_size; ASSERT(scalar_size == sizeof(Scalar)); - size_t size_size; + size_t size_size = 0; s >> size_size; ASSERT(size_size == sizeof(Size)); reset(); - owner_.reset(new detail::StandardAllocator()); + owner_ = std::make_unique(); reserve(rows, cols, nnz); @@ -529,7 +547,7 @@ void SparseMatrix::decode(Stream& s) { << " rows " << rows << " cols " << cols << " nnz " << nnz << " footprint " << footprint() << std::endl; - s.readLargeBlob(spm_.outer_, shape_.outerSize() * sizeof(Index)); + s.readLargeBlob(spm_.outer_, shape_.outerSize() * sizeof(UIndex)); s.readLargeBlob(spm_.inner_, shape_.innerSize() * sizeof(Index)); s.readLargeBlob(spm_.data_, shape_.dataSize() * sizeof(Scalar)); } @@ -542,19 +560,12 @@ Stream& operator<<(Stream& s, const SparseMatrix& v) { SparseMatrix::const_iterator SparseMatrix::const_iterator::operator++(int) { - const_iterator it = *this; + auto it = *this; ++(*this); return it; } -SparseMatrix::const_iterator& SparseMatrix::const_iterator::operator=(const SparseMatrix::const_iterator& other) { - matrix_ = other.matrix_; - index_ = other.index_; - row_ = other.row_; - return *this; -} - bool SparseMatrix::const_iterator::operator==(const SparseMatrix::const_iterator& other) const { ASSERT(other.matrix_ == matrix_); return other.index_ == index_; @@ -562,27 +573,28 @@ bool SparseMatrix::const_iterator::operator==(const SparseMatrix::const_iterator SparseMatrix::const_iterator::const_iterator(const SparseMatrix& matrix) : - matrix_(const_cast(&matrix)), index_(0), row_(0) { - const Index* outer = matrix_->outer(); - while (outer[row_ + 1] == 0) { + matrix_(const_cast(&matrix)), index_(0) { + for (row_ = 0; matrix_->spm_.outer_[row_ + 1] == 0;) { ++row_; } } + SparseMatrix::const_iterator::const_iterator(const SparseMatrix& matrix, Size row) : matrix_(const_cast(&matrix)), row_(row) { - const Size rows = matrix_->rows(); - if (row_ > rows) { + if (const Size rows = matrix_->rows(); row_ > rows) { row_ = rows; } - index_ = Size(matrix_->outer()[row_]); + index_ = static_cast(matrix_->spm_.outer_[row_]); } + Size SparseMatrix::const_iterator::col() const { - assert(matrix_ && index_ < matrix_->nonZeros()); - return Size(matrix_->inner()[index_]); + ASSERT(matrix_ && index_ < matrix_->nonZeros()); + return static_cast(matrix_->inner()[index_]); } + Size SparseMatrix::const_iterator::row() const { return row_; } @@ -599,11 +611,13 @@ SparseMatrix::const_iterator& SparseMatrix::const_iterator::operator++() { const Scalar& SparseMatrix::const_iterator::operator*() const { assert(matrix_ && index_ < matrix_->nonZeros()); - return matrix_->spm_.data_[index_]; + return matrix_->data()[index_]; } -void eckit::linalg::SparseMatrix::const_iterator::print(std::ostream& os) const { - os << "SparseMatrix::iterator(row=" << row_ << ", index=" << index_ << ")" << std::endl; + +void SparseMatrix::const_iterator::print(std::ostream& os) const { + os << "SparseMatrix::iterator(row=" << row_ << ", col=" << col() << ", index=" << index_ + << ", value=" << operator*() << ")" << std::endl; } @@ -614,7 +628,7 @@ Scalar& SparseMatrix::iterator::operator*() { //---------------------------------------------------------------------------------------------------------------------- -SparseMatrix::Allocator::~Allocator() {} +SparseMatrix::Allocator::~Allocator() = default; //---------------------------------------------------------------------------------------------------------------------- diff --git a/src/eckit/linalg/SparseMatrix.h b/src/eckit/linalg/SparseMatrix.h index 891ab485e..123b14007 100644 --- a/src/eckit/linalg/SparseMatrix.h +++ b/src/eckit/linalg/SparseMatrix.h @@ -15,21 +15,19 @@ #pragma once -#include #include #include +#include #include -#include "eckit/io/MemoryHandle.h" #include "eckit/linalg/Triplet.h" #include "eckit/linalg/types.h" -#include "eckit/memory/MemoryBuffer.h" -#include "eckit/memory/NonCopyable.h" namespace eckit { -class Stream; +class MemoryBuffer; class PathName; +class Stream; } // namespace eckit namespace eckit::linalg { @@ -38,11 +36,12 @@ namespace eckit::linalg { /// Sparse matrix in CRS (compressed row storage) format class SparseMatrix { -public: // types - struct Layout { +public: + using UIndex = std::make_unsigned_t; + - Layout() : - data_(nullptr), outer_(nullptr), inner_(nullptr) {} + struct Layout { + Layout() = default; void reset() { data_ = nullptr; @@ -50,16 +49,14 @@ class SparseMatrix { inner_ = nullptr; } - Scalar* data_; ///< matrix entries, sized with number of non-zeros (nnz) - Index* outer_; ///< start of rows, sized number of rows + 1 - Index* inner_; ///< column indices, sized with number of non-zeros (nnz) + Scalar* data_ = nullptr; ///< matrix entries, sized with number of non-zeros (nnz) + UIndex* outer_ = nullptr; ///< start of rows, sized number of rows + 1 + Index* inner_ = nullptr; ///< column indices, sized with number of non-zeros (nnz) }; struct Shape { - - Shape() : - size_(0), rows_(0), cols_(0) {} + Shape() = default; void reset() { size_ = 0; @@ -76,31 +73,26 @@ class SparseMatrix { /// @returns number of non-zeros Size nonZeros() const { return size_; } - /// data size is the number of non-zeros + /// @returns number of non-zeros Size dataSize() const { return nonZeros(); } - /// inner size is the number of non-zeros + /// @returns number of non-zeros Size innerSize() const { return nonZeros(); } /// @returns outer size is number of rows + 1 - Size outerSize() const { return Size(rows_ + 1); } + Size outerSize() const { return rows_ + 1; } size_t allocSize() const { return sizeofData() + sizeofOuter() + sizeofInner(); } size_t sizeofData() const { return dataSize() * sizeof(Scalar); } - size_t sizeofOuter() const { return outerSize() * sizeof(Index); } + size_t sizeofOuter() const { return outerSize() * sizeof(UIndex); } size_t sizeofInner() const { return innerSize() * sizeof(Index); } - Size size_; ///< Size of the container (AKA number of non-zeros nnz) - Size rows_; ///< Number of rows - Size cols_; ///< Number of columns + Size size_ = 0; ///< Size of the container (AKA number of non-zeros nnz) + Size rows_ = 0; ///< Number of rows + Size cols_ = 0; ///< Number of columns - void print(std::ostream& os) const { - os << "Shape[" - << "nnz=" << size_ << "," - << "rows=" << rows_ << "," - << "cols=" << cols_ << "]"; - } + void print(std::ostream&) const; friend std::ostream& operator<<(std::ostream& os, const Shape& p) { p.print(os); @@ -116,10 +108,9 @@ class SparseMatrix { /// @note that shape may be modified by the allocator, e.g. loading of pre-computed matrices virtual Layout allocate(Shape&) = 0; - /// Layout and Shape parameters may be ignored virtual void deallocate(Layout, Shape) = 0; - /// Is the memory shared + /// @returns if allocation is in shared memory virtual bool inSharedMemory() const = 0; virtual void print(std::ostream&) const = 0; @@ -130,23 +121,23 @@ class SparseMatrix { } }; -public: // methods +public: // -- Constructors /// Default constructor, empty matrix - SparseMatrix(Allocator* alloc = nullptr); + explicit SparseMatrix(Allocator* = nullptr); /// Constructs an identity matrix with provided dimensions - SparseMatrix(Size rows, Size cols, Allocator* alloc = nullptr); + SparseMatrix(Size rows, Size cols, Allocator* = nullptr); /// Constructor from triplets - SparseMatrix(Size rows, Size cols, const std::vector& triplets); + SparseMatrix(Size rows, Size cols, const std::vector&); /// Constructor from Stream - SparseMatrix(Stream& v); + explicit SparseMatrix(Stream&); /// Constructor from MemoryBuffer - SparseMatrix(const MemoryBuffer&); + explicit SparseMatrix(const MemoryBuffer&); /// Move constructor SparseMatrix(SparseMatrix&&); @@ -154,14 +145,18 @@ class SparseMatrix { /// Copy constructor SparseMatrix(const SparseMatrix&); + /// Destructor ~SparseMatrix(); /// Assignment operator (allocates and copies data) SparseMatrix& operator=(const SparseMatrix&); + /// Assignment operator (moves data) + SparseMatrix& operator=(SparseMatrix&&); + public: /// Prune entries with exactly the given value - SparseMatrix& prune(Scalar val = Scalar(0)); + SparseMatrix& prune(Scalar = 0); /// Set matrix to the identity SparseMatrix& setIdentity(Size rows, Size cols); @@ -174,15 +169,15 @@ class SparseMatrix { // -- I/O - void save(const eckit::PathName& path) const; - void load(const eckit::PathName& path); + void save(const eckit::PathName&) const; + void load(const eckit::PathName&); - void dump(eckit::MemoryBuffer& buffer) const; + void dump(eckit::MemoryBuffer&) const; void dump(void* buffer, size_t size) const; - static void load(const void* buffer, size_t bufferSize, Layout& layout, Shape& shape); ///< from dump() + static void load(const void* buffer, size_t bufferSize, Layout&, Shape&); ///< from dump() - void swap(SparseMatrix& other); + void swap(SparseMatrix&); /// @returns number of rows Size rows() const { return shape_.rows_; } @@ -194,13 +189,16 @@ class SparseMatrix { Size nonZeros() const { return shape_.size_; } /// @returns true if this matrix does not contain non-zero entries - bool empty() const { return !nonZeros(); } + bool empty() const { return nonZeros() == 0; } + + /// @returns read-only view of the outer index vector + const UIndex* outerIndex() const { return spm_.outer_; } /// @returns read-only view of the data vector const Scalar* data() const { return spm_.data_; } - /// @returns read-only view of the outer index vector - const Index* outer() const { return spm_.outer_; } + /// @returns read-only view of the outer index vector (signed) + const Index* outer() const; /// @returns read-only view of the inner index vector const Index* inner() const { return spm_.inner_; } @@ -209,13 +207,12 @@ class SparseMatrix { void cols(Size cols); /// Reserve memory for given number of non-zeros (invalidates all data arrays) - /// @note variables into this method must be by value void reserve(Size rows, Size cols, Size nnz); - /// Returns the footprint of the matrix in memory + /// @returns footprint of the matrix in memory size_t footprint() const; - /// Is the memory shared + /// @returns if allocation is in shared memory bool inSharedMemory() const; void dump(std::ostream&) const; @@ -230,12 +227,16 @@ class SparseMatrix { } public: // iterators + struct iterator; + struct const_iterator { + const_iterator(const SparseMatrix&); + const_iterator(const SparseMatrix&, Size row); - const_iterator(const SparseMatrix& matrix); - const_iterator(const SparseMatrix& matrix, Size row); + const_iterator(const const_iterator&) = default; + const_iterator(const_iterator&&) = default; - const_iterator(const const_iterator& other) { *this = other; } + virtual ~const_iterator() = default; Size col() const; Size row() const; @@ -244,63 +245,64 @@ class SparseMatrix { const_iterator& operator++(); const_iterator operator++(int); - const_iterator& operator=(const const_iterator& other); + + const_iterator& operator=(const const_iterator&) = default; + const_iterator& operator=(const_iterator&&) = default; bool operator!=(const const_iterator& other) const { return !operator==(other); } - bool operator==(const const_iterator& other) const; + bool operator==(const const_iterator&) const; const Scalar& operator*() const; void print(std::ostream&) const; - bool lastOfRow() const { return ((index_ + 1) == Size(matrix_->outer()[row_ + 1])); } + bool lastOfRow() const { return ((index_ + 1) == static_cast(matrix_->spm_.outer_[row_ + 1])); } + + private: + friend struct iterator; - protected: SparseMatrix* matrix_; Size index_; Size row_; }; - struct iterator : const_iterator { - iterator(SparseMatrix& matrix) : - const_iterator(matrix) {} - iterator(SparseMatrix& matrix, Size row) : - const_iterator(matrix, row) {} + struct iterator final : const_iterator { + using const_iterator::const_iterator; Scalar& operator*(); }; /// const iterators to begin/end of row - const_iterator begin(Size row) const { return const_iterator(*this, row); } - const_iterator end(Size row) const { return const_iterator(*this, row + 1); } + const_iterator begin(Size row) const { return {*this, row}; } + const_iterator end(Size row) const { return {*this, row + 1}; } /// const iterators to begin/end of matrix - const_iterator begin() const { return const_iterator(*this); } - const_iterator end() const { return const_iterator(*this, rows()); } + const_iterator begin() const { return {*this}; } + const_iterator end() const { return {*this, rows()}; } /// iterators to begin/end of row - iterator begin(Size row) { return iterator(*this, row); } - iterator end(Size row) { return iterator(*this, row + 1); } + iterator begin(Size row) { return {*this, row}; } + iterator end(Size row) { return {*this, row + 1}; } /// const iterators to begin/end of matrix - iterator begin() { return iterator(*this); } - iterator end() { return iterator(*this, rows()); } + iterator begin() { return {*this}; } + iterator end() { return {*this, rows()}; } -private: // methods +private: /// Resets the matrix to a deallocated state void reset(); /// Serialise to a Stream - void encode(Stream& s) const; + void encode(Stream&) const; /// Deserialise from a Stream - void decode(Stream& s); + void decode(Stream&); -private: // members - Layout spm_; +private: + Layout spm_; ///< Matrix layout - Shape shape_; + Shape shape_; ///< Matrix shape - std::unique_ptr owner_; ///< memory manager / allocator + std::unique_ptr owner_; ///< Matrix memory manager/allocator friend Stream& operator<<(Stream&, const SparseMatrix&); }; diff --git a/src/eckit/linalg/dense/LinearAlgebraMKL.cc b/src/eckit/linalg/dense/LinearAlgebraMKL.cc index ef731fe0d..4b7316be6 100644 --- a/src/eckit/linalg/dense/LinearAlgebraMKL.cc +++ b/src/eckit/linalg/dense/LinearAlgebraMKL.cc @@ -42,7 +42,7 @@ void LinearAlgebraMKL::print(std::ostream& out) const { Scalar LinearAlgebraMKL::dot(const Vector& x, const Vector& y) const { ASSERT(x.size() == y.size()); - const auto n = static_cast(x.size()); + const auto n = static_cast(x.size()); const auto* _x = static_cast(x.data()); const auto* _y = static_cast(y.data()); @@ -57,8 +57,8 @@ void LinearAlgebraMKL::gemv(const Matrix& A, const Vector& x, Vector& y) const { ASSERT(x.size() == A.cols()); ASSERT(y.size() == A.rows()); - const auto m = static_cast(A.rows()); - const auto n = static_cast(A.cols()); + const auto m = static_cast(A.rows()); + const auto n = static_cast(A.cols()); const auto* _A = static_cast(A.data()); const auto* _x = static_cast(x.data()); @@ -79,9 +79,9 @@ void LinearAlgebraMKL::gemm(const Matrix& A, const Matrix& B, Matrix& C) const { ASSERT(A.rows() == C.rows()); ASSERT(B.cols() == C.cols()); - const auto m = static_cast(A.rows()); - const auto n = static_cast(B.cols()); - const auto k = static_cast(A.cols()); + const auto m = static_cast(A.rows()); + const auto n = static_cast(B.cols()); + const auto k = static_cast(A.cols()); const auto* _A = static_cast(A.data()); const auto* _B = static_cast(B.data()); diff --git a/src/eckit/linalg/sparse/LinearAlgebraEigen.cc b/src/eckit/linalg/sparse/LinearAlgebraEigen.cc index 39b9fd1d5..34159cca8 100644 --- a/src/eckit/linalg/sparse/LinearAlgebraEigen.cc +++ b/src/eckit/linalg/sparse/LinearAlgebraEigen.cc @@ -26,7 +26,7 @@ static const LinearAlgebraEigen __la("eigen"); using vec_t = Eigen::VectorXd::MapType; using mat_t = Eigen::MatrixXd::MapType; -using spm_t = Eigen::MappedSparseMatrix; +using spm_t = Eigen::Map>; void LinearAlgebraEigen::print(std::ostream& out) const { diff --git a/src/eckit/linalg/sparse/LinearAlgebraGeneric.cc b/src/eckit/linalg/sparse/LinearAlgebraGeneric.cc index 22cd509f0..eb79e4547 100644 --- a/src/eckit/linalg/sparse/LinearAlgebraGeneric.cc +++ b/src/eckit/linalg/sparse/LinearAlgebraGeneric.cc @@ -44,7 +44,7 @@ void LinearAlgebraGeneric::spmv(const SparseMatrix& A, const Vector& x, Vector& return; } - const auto* const outer = A.outer(); + const auto* const outer = A.outerIndex(); const auto* const inner = A.inner(); const auto* const val = A.data(); @@ -78,7 +78,7 @@ void LinearAlgebraGeneric::spmm(const SparseMatrix& A, const Matrix& B, Matrix& return; } - const auto* const outer = A.outer(); + const auto* const outer = A.outerIndex(); const auto* const inner = A.inner(); const auto* const val = A.data(); diff --git a/src/eckit/linalg/sparse/LinearAlgebraMKL.cc b/src/eckit/linalg/sparse/LinearAlgebraMKL.cc index e85e003c6..9e0a62206 100644 --- a/src/eckit/linalg/sparse/LinearAlgebraMKL.cc +++ b/src/eckit/linalg/sparse/LinearAlgebraMKL.cc @@ -71,9 +71,9 @@ void LinearAlgebraMKL::spmm(const SparseMatrix& A, const Matrix& B, Matrix& C) c // We expect indices to be 0-based ASSERT(A.outer()[0] == 0); - const auto m = static_cast(A.rows()); - const auto n = static_cast(C.cols()); - const auto k = static_cast(A.cols()); + const auto m = static_cast(A.rows()); + const auto n = static_cast(C.cols()); + const auto k = static_cast(A.cols()); // FIXME: with 0-based indexing, MKL assumes row-major ordering for B and C // We need to use 1-based indexing i.e. offset outer and inner indices by 1 diff --git a/src/eckit/log/CodeLocation.cc b/src/eckit/log/CodeLocation.cc index 50307561b..f6384ccdc 100644 --- a/src/eckit/log/CodeLocation.cc +++ b/src/eckit/log/CodeLocation.cc @@ -34,8 +34,8 @@ CodeLocation::operator bool() const { void CodeLocation::print(std::ostream& os) const { if (file_) { - os << " (" << file_ << " +" << line_; - if (func_ && ::strlen(func_)) { + os << " (" << file_ << ":" << line_; + if (func_ && ::strlen(func_) > 0) { os << " " << func_; } os << ")"; diff --git a/src/eckit/log/Colour.cc b/src/eckit/log/Colour.cc index 084b6f56d..a36d3704f 100644 --- a/src/eckit/log/Colour.cc +++ b/src/eckit/log/Colour.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include #include diff --git a/src/eckit/log/Log.cc b/src/eckit/log/Log.cc index 767b6ee52..acb07dc24 100644 --- a/src/eckit/log/Log.cc +++ b/src/eckit/log/Log.cc @@ -173,9 +173,10 @@ struct CreateDebugChannel : public CreateLogChannel { }; Channel& Log::debug() { - if (!Main::ready()) { + if (!Main::ready()) { + const char* e = getenv("DEBUG"); if (e && bool(Translator()(e))) { @@ -187,8 +188,8 @@ Channel& Log::debug() { } if (!Main::instance().debug_) { - static Channel empty; - return empty; + static ThreadSingleton empty; + return empty.instance(); } diff --git a/src/eckit/log/Seconds.h b/src/eckit/log/Seconds.h index 4ebf13ef8..61ac52fe6 100644 --- a/src/eckit/log/Seconds.h +++ b/src/eckit/log/Seconds.h @@ -15,7 +15,7 @@ #define eckit_Seconds_h #include -#include +#include #include //----------------------------------------------------------------------------- diff --git a/src/eckit/log/SysLog.cc b/src/eckit/log/SysLog.cc index 98c5ffc54..e10304ad3 100644 --- a/src/eckit/log/SysLog.cc +++ b/src/eckit/log/SysLog.cc @@ -18,6 +18,7 @@ #include "eckit/runtime/Main.h" #include "eckit/log/TimeStamp.h" +#include "eckit/net/IPAddress.h" namespace eckit { @@ -43,11 +44,26 @@ int SysLog::procid() const { return ::getpid(); } + std::string SysLog::structuredData() const { + if (software_.empty() && swVersion_.empty() && enterpriseId_.empty()) { + return std::string(1, nilvalue()); + } + // RFC 5424 section 6.3 (only origin) std::ostringstream s; - - /// @todo Implement the structured message meta-description as described in RFC5424 secion 6.3 - s << nilvalue(); + std::string ip = net::IPAddress::myIPAddress().asString(); + + s << "[origin ip=\"" << ip << "\""; + if (!enterpriseId_.empty()) { + s << " enterpriseId=\"" << enterpriseId_ << "\""; + } + if (!software_.empty()) { + s << " software=\"" << software_ << "\""; + if (!swVersion_.empty()) { + s << " swVersion=\"" << swVersion_ << "\""; + } + } + s << "]"; return s.str(); } @@ -59,7 +75,7 @@ SysLog::operator std::string() const { static char sep = ' '; os // RFC 5424 section 6.2 (Header) - << "<" << priotity() << ">" << version() << sep << timestamp() << sep << fqdn() << sep << appName() << sep + << "<" << priority() << ">" << version() << sep << timestamp() << sep << fqdn() << sep << appName() << sep << procid() << sep << msgid() << sep // RFC 5424 section 6.3 diff --git a/src/eckit/log/SysLog.h b/src/eckit/log/SysLog.h index 28931e295..d88c31f53 100644 --- a/src/eckit/log/SysLog.h +++ b/src/eckit/log/SysLog.h @@ -72,7 +72,7 @@ class SysLog { SysLog(const std::string& msg, int msgid = 0, Facility f = SysLog::User, Severity s = SysLog::Info); - unsigned priotity() const { return facility_ * 8 + severity_; } + unsigned priority() const { return facility_ * 8 + severity_; } unsigned version() const { return 1; } @@ -99,6 +99,11 @@ class SysLog { return s; } + /// Optional fields for structured data (RFC 5424 section 6.3) + void software(const std::string& software) { software_ = software; } + void swVersion(const std::string& version) { swVersion_ = version; } + void enterpriseId(const std::string& id) { enterpriseId_ = id; } + private: // methods void print(std::ostream& out) const; @@ -112,6 +117,11 @@ class SysLog { int msgid_; std::string msg_; + + // optional fields for structured data + std::string software_; + std::string swVersion_; + std::string enterpriseId_; }; } // namespace eckit diff --git a/src/eckit/log/TimeStamp.cc b/src/eckit/log/TimeStamp.cc index 057d6a52a..be5a5a41d 100644 --- a/src/eckit/log/TimeStamp.cc +++ b/src/eckit/log/TimeStamp.cc @@ -9,6 +9,8 @@ */ #include +#include +#include #include "eckit/eckit.h" @@ -27,6 +29,12 @@ TimeStamp::TimeStamp(time_t t, const std::string& format) : time_(t), format_(format) {} std::ostream& operator<<(std::ostream& s, const TimeStamp& x) { + + if (x.format_ == "hex") { + s << std::setw(16) << std::setfill('0') << std::hex << static_cast(x.time_); + return s; + } + char buf[80]; #if eckit_HAVE_GMTIME_R struct tm t; diff --git a/src/eckit/log/TimeStamp.h b/src/eckit/log/TimeStamp.h index 34ccc7e8b..46705fbcf 100644 --- a/src/eckit/log/TimeStamp.h +++ b/src/eckit/log/TimeStamp.h @@ -15,7 +15,7 @@ #ifndef eckit_log_TimeStamp_h #define eckit_log_TimeStamp_h -#include +#include #include #include diff --git a/src/eckit/log/Timer.cc b/src/eckit/log/Timer.cc index d75abb4a6..d356791fe 100644 --- a/src/eckit/log/Timer.cc +++ b/src/eckit/log/Timer.cc @@ -89,6 +89,12 @@ void Timer::report(const std::string& message) { << std::endl; } +void Timer::reset(const std::string& message) { + stop(); + report(message); + start(); +} + void Timer::takeTime() { cpuStop_ = ::clock(); ::gettimeofday(&timeStop_, 0); diff --git a/src/eckit/log/Timer.h b/src/eckit/log/Timer.h index b871f1cc1..5d1984580 100644 --- a/src/eckit/log/Timer.h +++ b/src/eckit/log/Timer.h @@ -15,7 +15,7 @@ #define eckit_Timer_h #include -#include +#include #include "eckit/log/Log.h" #include "eckit/memory/NonCopyable.h" @@ -49,6 +49,7 @@ class Timer : private NonCopyable { bool running() const { return !stopped_; } void report(const std::string& message = ""); + void reset(const std::string& message = ""); protected: // methods void takeTime(); diff --git a/src/eckit/maths/CMakeLists.txt b/src/eckit/maths/CMakeLists.txt index 9957b5af2..26d971030 100644 --- a/src/eckit/maths/CMakeLists.txt +++ b/src/eckit/maths/CMakeLists.txt @@ -1,16 +1,35 @@ -list( APPEND eckit_maths_lib_srcs -Eigen.h -Lapack.h -Lapack.cc -Matrix.h -MatrixEigen.h -MatrixLapack.h +list(APPEND eckit_maths_private_libs "${LAPACK_LIBRARIES}" "${BLAS_LIBRARIES}") +list(APPEND eckit_maths_sources + Eigen.h + Lapack.cc + Lapack.h + Matrix.h + Matrix3.h + MatrixEigen.h + MatrixLapack.h ) -ecbuild_add_library( TARGET eckit_maths TYPE SHARED - INSTALL_HEADERS ALL - HEADER_DESTINATION ${INSTALL_INCLUDE_DIR}/eckit/maths - SOURCES ${eckit_maths_lib_srcs} - PUBLIC_INCLUDES "${EIGEN3_INCLUDE_DIRS}" - PUBLIC_LIBS eckit - PRIVATE_LIBS "${LAPACK_LIBRARIES}" "${BLAS_LIBRARIES}" ) +if(eckit_HAVE_CONVEX_HULL) + list(APPEND eckit_maths_sources ConvexHull.h ConvexHullN.h Qhull.cc Qhull.h) + list(APPEND eckit_maths_private_libs Qhull::Qhull) +endif() + +ecbuild_add_library( + TARGET eckit_maths + TYPE SHARED + INSTALL_HEADERS ALL + HEADER_DESTINATION ${INSTALL_INCLUDE_DIR}/eckit/maths + SOURCES ${eckit_maths_sources} + PRIVATE_LIBS ${eckit_maths_private_libs} + PUBLIC_LIBS eckit +) + +if(eckit_HAVE_EIGEN) + if(NOT EIGEN3_INCLUDE_DIRS AND TARGET Eigen3::Eigen) + get_target_property(EIGEN3_INCLUDE_DIRS Eigen3::Eigen INTERFACE_INCLUDE_DIRECTORIES) + endif() + + # Add include directories with "SYSTEM" to avoid warnings from within Eigen headers + target_include_directories(eckit_maths SYSTEM PUBLIC ${EIGEN3_INCLUDE_DIRS}) +endif() + diff --git a/src/eckit/maths/ConvexHull.h b/src/eckit/maths/ConvexHull.h new file mode 100644 index 000000000..964e7e7a8 --- /dev/null +++ b/src/eckit/maths/ConvexHull.h @@ -0,0 +1,86 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/maths/Qhull.h" + + +namespace eckit::maths { + + +class ConvexHull { +public: + // -- Types + + using coord_t = typename Qhull::coord_t; + using facets_n_t = typename Qhull::facets_n_t; + + struct Exception : eckit::Exception { + Exception(const std::string& what, int _errorCode, const std::string& _command) : + eckit::Exception(what), errorCode(_errorCode), command(_command) {} + const int errorCode; + const std::string& command; + }; + + struct DimensionError : Exception { + using Exception::Exception; + }; + + struct InputError : Exception { + using Exception::Exception; + }; + + struct OptionError : Exception { + using Exception::Exception; + }; + + struct PrecisionError : Exception { + using Exception::Exception; + }; + + struct TopologyError : Exception { + using Exception::Exception; + }; + + // -- Constructors + + ConvexHull(const ConvexHull&) = delete; + ConvexHull(ConvexHull&&) = delete; + + // -- Destructor + + virtual ~ConvexHull() = default; + + // -- Operators + + void operator=(const ConvexHull&) = delete; + void operator=(ConvexHull&&) = delete; + + // -- Methods + + virtual std::vector list_vertices() const = 0; + virtual std::vector> list_facets() const = 0; + + virtual facets_n_t facets_n() const = 0; + virtual std::vector facets(size_t n) const = 0; + +protected: + // -- Constructors + + ConvexHull() /*noexcept*/ = default; +}; + + +} // namespace eckit::maths diff --git a/src/eckit/maths/ConvexHullN.h b/src/eckit/maths/ConvexHullN.h new file mode 100644 index 000000000..73aa75407 --- /dev/null +++ b/src/eckit/maths/ConvexHullN.h @@ -0,0 +1,66 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include + +#include "eckit/maths/ConvexHull.h" + + +namespace eckit::maths { + + +template +class ConvexHullN : public ConvexHull { +public: + explicit ConvexHullN(const ConvexHull::coord_t& coord, const std::string& qhull_command = Qhull::COMMAND_DEFAULT) : + qhull_(N, coord, qhull_command) {} + + explicit ConvexHullN(const std::vector>& coord_v, + const std::string& qhull_command = Qhull::COMMAND_DEFAULT) : + ConvexHullN(convert_vector_v(coord_v), qhull_command) {} + + explicit ConvexHullN(const std::vector>& coord_a, + const std::string& qhull_command = Qhull::COMMAND_DEFAULT) : + ConvexHullN(convert_vector_a(coord_a), qhull_command) {} + + std::vector list_vertices() const override { return qhull_.list_vertices(); } + std::vector> list_facets() const override { return qhull_.list_facets(); } + + ConvexHull::facets_n_t facets_n() const override { return qhull_.facets_n(); } + std::vector facets(size_t n) const override { return qhull_.facets(n); } + +private: + static coord_t convert_vector_v(const std::vector>& coord_v) { + coord_t coord; + coord.reserve(N * coord_v.size()); + for (const auto& v : coord_v) { + ASSERT(N == v.size()); + std::for_each_n(v.begin(), N, [&coord](auto x) { coord.emplace_back(x); }); + } + return coord; + } + + static coord_t convert_vector_a(const std::vector>& coord_a) { + coord_t coord; + coord.reserve(N * coord_a.size()); + for (const auto& a : coord_a) { + std::for_each_n(a.begin(), N, [&coord](auto x) { coord.emplace_back(x); }); + } + return coord; + } + + Qhull qhull_; +}; + + +} // namespace eckit::maths diff --git a/src/eckit/maths/Matrix3.h b/src/eckit/maths/Matrix3.h new file mode 100644 index 000000000..669345d06 --- /dev/null +++ b/src/eckit/maths/Matrix3.h @@ -0,0 +1,112 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/Point3.h" + + +namespace eckit::maths { + + +template +class Matrix3 final : protected std::array { +public: + // -- Types + + using container_type = std::array; + +public: + // -- Constructors + + Matrix3(T xx, T xy, T xz, T yx, T yy, T yz, T zx, T zy, T zz) : + container_type{xx, xy, xz, yx, yy, yz, zx, zy, zz} {} + + Matrix3(const Matrix3& other) : container_type(other) {} + Matrix3(Matrix3&& other) : container_type(other) {} + + // -- Destructor + + ~Matrix3() = default; + + // -- Operators + + Matrix3& operator=(const Matrix3& other) { + container_type::operator=(other); + return *this; + } + + Matrix3& operator=(Matrix3&& other) { + container_type::operator=(other); + return *this; + } + + geo::Point3 operator*(const geo::Point3& p) const { + return {XX * p.X + XY * p.Y + XZ * p.Z, YX * p.X + YY * p.Y + YZ * p.Z, ZX * p.X + ZY * p.Y + ZZ * p.Z}; + } + + Matrix3 operator*(const Matrix3& M) const { + return { + XX * M.XX + XY * M.YX + XZ * M.ZX, XX * M.XY + XY * M.YY + XZ * M.ZY, XX * M.XZ + XY * M.YZ + XZ * M.ZZ, + YX * M.XX + YY * M.YX + YZ * M.ZX, YX * M.XY + YY * M.YY + YZ * M.ZY, YX * M.XZ + YY * M.YZ + YZ * M.ZZ, + ZX * M.XX + ZY * M.YX + ZZ * M.ZX, ZX * M.XY + ZY * M.YY + ZZ * M.ZY, ZX * M.XZ + ZY * M.YZ + ZZ * M.ZZ}; + } + + // -- Members + + const T& XX = container_type::operator[](0); + const T& XY = container_type::operator[](1); + const T& XZ = container_type::operator[](2); + const T& YX = container_type::operator[](3); + const T& YY = container_type::operator[](4); + const T& YZ = container_type::operator[](5); + const T& ZX = container_type::operator[](6); + const T& ZY = container_type::operator[](7); + const T& ZZ = container_type::operator[](8); + + // -- Methods + + using container_type::begin; + using container_type::cbegin; + using container_type::cend; + using container_type::end; + using container_type::size; + + static Matrix3 identity() { return {1, 0, 0, 0, 1, 0, 0, 0, 1}; } + + Matrix3 inverse() const { + auto det = XX * (YY * ZZ - YZ * ZY) - XY * (YX * ZZ - YZ * ZX) + XZ * (YX * ZY - YY * ZX); + ASSERT_MSG(det != 0, "Matrix3: singular matrix"); + + return {(YY * ZZ - YZ * ZY) / det, (XZ * ZY - XY * ZZ) / det, (XY * YZ - XZ * YY) / det, + (YZ * ZX - YX * ZZ) / det, (XX * ZZ - XZ * ZX) / det, (XZ * YX - XX * YZ) / det, + (YX * ZY - YY * ZX) / det, (XY * ZX - XX * ZY) / det, (XX * YY - XY * YX) / det}; + } + + T determinant() const { + return XX * YY * ZZ - XZ * YY * ZX + XY * YZ * ZX + XZ * YX * ZY - XX * YZ * ZY - XY * YX * ZZ; + } + + // -- Friends + + friend std::ostream& operator<<(std::ostream& out, const Matrix3& m) { + return out << "{{" << m.XX << ", " << m.XY << ", " << m.XZ << "}, {" << m.YX << ", " << m.YY << ", " << m.YZ + << "}, {" << m.ZX << ", " << m.ZY << ", " << m.ZZ << "}}"; + } +}; + + +} // namespace eckit::maths diff --git a/src/eckit/maths/Qhull.cc b/src/eckit/maths/Qhull.cc new file mode 100644 index 000000000..bf81fa1f4 --- /dev/null +++ b/src/eckit/maths/Qhull.cc @@ -0,0 +1,144 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/maths/Qhull.h" + +#include +#include + +#include "eckit/log/Log.h" +#include "eckit/maths/ConvexHull.h" + +#include "libqhullcpp/Qhull.h" +#include "libqhullcpp/QhullFacetList.h" +#include "libqhullcpp/QhullVertexSet.h" + + +namespace eckit::maths { + + +const char* Qhull::COMMAND_DEFAULT = "QJ"; + + +Qhull::Qhull(size_t N, const coord_t& coord, const std::string& command) { + ASSERT(0 < N && coord.size() % N == 0); + + auto pointDimension = static_cast(N); + auto pointCount = static_cast(coord.size() / N); + + qh_ = new orgQhull::Qhull; + ASSERT(qh_ != nullptr); + + std::ostringstream err; + qh_->setErrorStream(&err); + qh_->setOutputStream(&Log::info()); + qh_->enableOutputStream(); + + try { + qh_->runQhull("", pointDimension, pointCount, coord.data(), command.c_str()); + ASSERT(qh_->qhullStatus() == 0); + } + catch (const orgQhull::QhullError& e) { + static const std::set DIMENSION_ERROR{6050}; + + static const std::set INPUT_ERROR{6010, 6013, 6019, 6020, 6021, 6022, 6023, 6033, 6067, 6070, + 6072, 6073, 6074, 6075, 6077, 6078, 6150, 6151, 6152, 6153, + 6203, 6204, 6214, 6220, 6221, 6222, 6223, 6229, 6411, 6412}; + + static const std::set OPTION_ERROR{6006, 6029, 6035, 6036, 6037, 6041, 6044, 6045, 6046, + 6047, 6048, 6049, 6051, 6053, 6054, 6055, 6056, 6057, + 6058, 6059, 6215, 6362, 6363, 6364, 6365, 6375}; + + static const std::set PRECISION_ERROR{6012, 6109, 6110, 6111, 6112, 6113, 6114, 6115, 6116, + 6117, 6118, 6136, 6154, 6239, 6240, 6297, 6298, 6347, + 6348, 6354, 6379, 6380, 6417, 6418, 6422}; + + static const std::set TOPOLOGY_ERROR{6001, 6107, 6155, 6168, 6170, 6208, 6227, 6260, + 6271, 6356, 6361, 6381, 6391, 6420, 6425}; + + auto is = [](int err, const auto& set) { return set.find(err) != set.end(); }; + + is(e.errorCode(), DIMENSION_ERROR) ? throw ConvexHull::DimensionError(err.str(), e.errorCode(), command) + : is(e.errorCode(), INPUT_ERROR) ? throw ConvexHull::InputError(err.str(), e.errorCode(), command) + : is(e.errorCode(), OPTION_ERROR) ? throw ConvexHull::OptionError(err.str(), e.errorCode(), command) + : is(e.errorCode(), PRECISION_ERROR) ? throw ConvexHull::PrecisionError(err.str(), e.errorCode(), command) + : is(e.errorCode(), TOPOLOGY_ERROR) ? throw ConvexHull::TopologyError(err.str(), e.errorCode(), command) + : throw ConvexHull::Exception(err.str(), e.errorCode(), command); + } +} + + +Qhull::~Qhull() { + delete qh_; +} + + +std::vector Qhull::list_vertices() const { + std::vector vertices; + vertices.reserve(qh_->vertexCount()); + + for (const auto& vertex : qh_->vertexList()) { + vertices.emplace_back(vertex.point().id()); + } + + return vertices; +} + + +std::vector> Qhull::list_facets() const { + std::vector> facets; + facets.reserve(qh_->facetCount()); + + for (const auto& facet : qh_->facetList()) { + const auto vertices = facet.vertices(); + + std::vector f; + f.reserve(vertices.size()); + + for (const auto& vertex : vertices) { + f.emplace_back(vertex.point().id()); + } + + facets.emplace_back(std::move(f)); + } + + return facets; +} + + +Qhull::facets_n_t Qhull::facets_n() const { + facets_n_t counts; + for (const auto& facet : qh_->facetList()) { + counts[facet.vertices().size()]++; + } + return counts; +} + + +std::vector Qhull::facets(size_t n) const { + ASSERT(0 < n); + + std::vector facets; + facets.reserve(qh_->facetCount() * n); + + for (const auto& facet : qh_->facetList()) { + if (const auto vertices = facet.vertices(); vertices.size() == n) { + for (const auto& vertex : vertices) { + facets.emplace_back(vertex.point().id()); + } + } + } + + return facets; +} + + +} // namespace eckit::maths diff --git a/src/eckit/maths/Qhull.h b/src/eckit/maths/Qhull.h new file mode 100644 index 000000000..2bbc08fe2 --- /dev/null +++ b/src/eckit/maths/Qhull.h @@ -0,0 +1,68 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#pragma once + + +#include +#include +#include + + +namespace orgQhull { +class Qhull; +} + + +namespace eckit::maths { + + +class Qhull { +public: + // -- Types + + using coord_t = std::vector; + using facets_n_t = std::map; + + static const char* COMMAND_DEFAULT; + + // -- Constructors + + Qhull(size_t N, const coord_t& coord, const std::string& command = COMMAND_DEFAULT); + + Qhull(const Qhull&) = delete; + Qhull(Qhull&&) = delete; + + // -- Destructor + + ~Qhull(); + + // -- Operators + + Qhull& operator=(const Qhull&) = delete; + Qhull& operator=(Qhull&&) = delete; + + // -- Methods + + std::vector list_vertices() const; + std::vector> list_facets() const; + + facets_n_t facets_n() const; + std::vector facets(size_t n) const; + +private: + // -- Members + + orgQhull::Qhull* qh_; +}; + + +} // namespace eckit::maths diff --git a/src/eckit/memory/Builder.h b/src/eckit/memory/Builder.h index 898e3ff39..5878a35ba 100644 --- a/src/eckit/memory/Builder.h +++ b/src/eckit/memory/Builder.h @@ -8,155 +8,178 @@ * does it submit to any jurisdiction. */ -#ifndef eckit_memory_Builder_h -#define eckit_memory_Builder_h - /// @file Builder.h /// @author Tiago Quintino +/// @author Pedro Maciel /// @date Jul 2014 -#include "eckit/exception/Exceptions.h" + +#pragma once + +#include "eckit/eckit_config.h" #include "eckit/memory/Factory.h" -#include "eckit/value/Params.h" -// #define DEBUG_ECKIT_BUILDERS -#ifdef DEBUG_ECKIT_BUILDERS +#if eckit_HAVE_ECKIT_MEMORY_FACTORY_BUILDERS_DEBUG +#include "eckit/exception/Exceptions.h" #define DEBUG_BUILDER(x) std::cerr << " DEBUG (" << x << ") " << Here() << std::endl; #else #define DEBUG_BUILDER(x) #endif -//----------------------------------------------------------------------------- namespace eckit { //------------------------------------------------------------------------------------------------------ -class Builder : private NonCopyable { +class Builder { public: - typedef std::string key_t; + // -- Types + + using key_t = std::string; + + // -- Constructors - virtual ~Builder(); + Builder() = default; + Builder(const Builder&) = delete; + Builder(Builder&&) = delete; + + // -- Destructor + + virtual ~Builder() = default; + + // -- Operators + + void operator=(const Builder&) = delete; + void operator=(Builder&&) = delete; + + // -- Methods virtual key_t name() const = 0; virtual key_t build_type() const = 0; +private: + // -- Methods + + virtual void print(std::ostream& os) const { os << "Builder(" << build_type() << "):" << name(); } + + // -- Friends + friend std::ostream& operator<<(std::ostream& os, const Builder& o) { o.print(os); return os; } - -private: // methods - virtual void print(std::ostream& os) const { os << "Builder(" << build_type() << "):" << name(); } }; //------------------------------------------------------------------------------------------------------ template class BuilderT0 : public Builder { +public: + // -- Types -public: // types - BuilderT0() {} + using product_t = Base; - ~BuilderT0() {} + // -- Methods - typedef Base product_t; - typedef product_t* product_ptr; - typedef Builder::key_t key_t; - typedef typename Factory::builder_ptr builder_ptr; + virtual product_t* create() const = 0; - virtual product_ptr create() const = 0; + // -- Overridden methods -public: // methods - virtual key_t build_type() const { return Base::className(); } + typename Builder::key_t build_type() const override { return Base::className(); } }; //------------------------------------------------------------------------------------------------------ template class BuilderT1 : public Builder { +public: + // -- Types -public: // types - BuilderT1() {} + using product_t = Base; - ~BuilderT1() {} + using ARG1 = typename product_t::ARG1; - typedef Base product_t; - typedef product_t* product_ptr; - typedef Builder::key_t key_t; - typedef typename Factory::builder_ptr builder_ptr; + // -- Methods - typedef typename product_t::ARG1 ARG1; + virtual product_t* create(ARG1) const = 0; - virtual product_ptr create(ARG1 p1) const = 0; + // -- Overridden methods -public: // methods - virtual key_t build_type() const { return Base::className(); } + typename Builder::key_t build_type() const override { return Base::className(); } }; //------------------------------------------------------------------------------------------------------ template class BuilderT2 : public Builder { +public: + // -- Types -public: // types - BuilderT2(){}; + using product_t = Base; - ~BuilderT2() {} + using ARG1 = typename product_t::ARG1; + using ARG2 = typename product_t::ARG2; - typedef Base product_t; - typedef product_t* product_ptr; - typedef Builder::key_t key_t; - typedef typename Factory::builder_ptr builder_ptr; + // -- Methods - typedef typename product_t::ARG1 ARG1; - typedef typename product_t::ARG2 ARG2; + virtual product_t* create(ARG1, ARG2) const = 0; - virtual product_ptr create(ARG1 p1, ARG2 p2) const = 0; + // -- Overridden methods -public: // methods - virtual key_t build_type() const { return Base::className(); } + typename Builder::key_t build_type() const override { return Base::className(); } }; + //------------------------------------------------------------------------------------------------------ template -class ConcreteBuilderT0 : public BuilderT0 { +class ConcreteBuilderT0 final : public BuilderT0 { +public: + // -- Types -public: // types - typedef BuilderT0 base_t; + using base_t = BuilderT0; - typedef typename base_t::key_t key_t; - typedef typename base_t::product_t product_t; - typedef typename base_t::product_ptr product_ptr; - typedef typename base_t::builder_ptr builder_ptr; + // -- Constructors -public: // methods - ConcreteBuilderT0() : - k_(name()) { + ConcreteBuilderT0() : key_(name()) { DEBUG_BUILDER("ConcreteBuilderT0() -- " << T::className()); - Factory::instance().regist(k_, builder_ptr(this)); + Factory::instance().regist(key_, this); } - ConcreteBuilderT0(const key_t& k) : - k_(k) { + explicit ConcreteBuilderT0(const typename base_t::key_t& k) : key_(k) { DEBUG_BUILDER("ConcreteBuilderT0() -- " << T::className()); - Factory::instance().regist(k_, builder_ptr(this)); + Factory::instance().regist(key_, this); } + ConcreteBuilderT0(const ConcreteBuilderT0&) = delete; + ConcreteBuilderT0(ConcreteBuilderT0&&) = delete; + + // -- Destructor + ~ConcreteBuilderT0() override { DEBUG_BUILDER("~ConcreteBuilderT0() -- " << T::className()); - Factory::instance().unregist(k_); +#if eckit_HAVE_ECKIT_MEMORY_FACTORY_EMPTY_DESTRUCTION + Factory::instance().unregist(key_); +#endif } - typename base_t::key_t name() const override { return T::className(); } + // -- Operators - product_ptr create() const override { return new T(); } + void operator=(const ConcreteBuilderT0&) = delete; + void operator=(ConcreteBuilderT0&&) = delete; + + // -- Overridden methods + + typename base_t::key_t name() const override { return T::className(); } + typename base_t::product_t* create() const override { return new T(); } private: - key_t k_; + // -- Members + + typename base_t::key_t key_; }; + #define register_BuilderT0(ABSTRACT, CONCRETE, NAME) \ static struct Register__##ABSTRACT##__##CONCRETE##__T0 { \ Register__##ABSTRACT##__##CONCRETE##__T0() { \ @@ -164,46 +187,58 @@ class ConcreteBuilderT0 : public BuilderT0 { } \ } register_##ABSTRACT##__##CONCRETE##_T0 + //------------------------------------------------------------------------------------------------------ template -class ConcreteBuilderT1 : public BuilderT1 { -public: // types - typedef BuilderT1 base_t; +class ConcreteBuilderT1 final : public BuilderT1 { +public: + // -- Types - typedef typename base_t::key_t key_t; - typedef typename base_t::product_t product_t; - typedef typename base_t::product_ptr product_ptr; - typedef typename base_t::builder_ptr builder_ptr; + using base_t = BuilderT1; - typedef typename base_t::ARG1 ARG1; + // -- Constructors -public: // methods - ConcreteBuilderT1() : - k_(name()) { + ConcreteBuilderT1() : key_(name()) { DEBUG_BUILDER("ConcreteBuilderT1() -- " << T::className()); - Factory::instance().regist(k_, builder_ptr(this)); + Factory::instance().regist(key_, this); } - ConcreteBuilderT1(const key_t& k) : - k_(k) { + explicit ConcreteBuilderT1(const typename base_t::key_t& k) : key_(k) { DEBUG_BUILDER("ConcreteBuilderT1() -- " << T::className()); - Factory::instance().regist(k_, builder_ptr(this)); + Factory::instance().regist(key_, this); } + ConcreteBuilderT1(const ConcreteBuilderT1&) = delete; + ConcreteBuilderT1(ConcreteBuilderT1&&) = delete; + + // -- Destructor + ~ConcreteBuilderT1() override { DEBUG_BUILDER("~ConcreteBuilderT1() -- " << T::className()); - Factory::instance().unregist(k_); +#if ECKIT_MEMORY_FACTORY_EMPTY_DESTRUCTION + Factory::instance().unregist(key_); +#endif } + // -- Operators + + void operator=(const ConcreteBuilderT1&) = delete; + void operator=(ConcreteBuilderT1&&) = delete; + + // -- Overridden methods + typename base_t::key_t name() const override { return T::className(); } + typename base_t::product_t* create(typename base_t::ARG1 p1) const override { return new T(p1); } - product_ptr create(ARG1 p1) const override { return new T(p1); } private: - key_t k_; + // -- Members + + typename base_t::key_t key_; }; + #define register_BuilderT1(ABSTRACT, CONCRETE, NAME) \ static struct Register__##ABSTRACT##__##CONCRETE##__T1 { \ Register__##ABSTRACT##__##CONCRETE##__T1() { \ @@ -211,48 +246,60 @@ class ConcreteBuilderT1 : public BuilderT1 { } \ } register_##ABSTRACT##__##CONCRETE##_T1 + //------------------------------------------------------------------------------------------------------ template -class ConcreteBuilderT2 : public BuilderT2 { - -public: // types - typedef BuilderT2 base_t; +class ConcreteBuilderT2 final : public BuilderT2 { +public: + // -- Types - typedef typename base_t::key_t key_t; - typedef typename base_t::product_t product_t; - typedef typename base_t::product_ptr product_ptr; - typedef typename base_t::builder_ptr builder_ptr; + using base_t = BuilderT2; - typedef typename base_t::ARG1 ARG1; - typedef typename base_t::ARG2 ARG2; + // -- Constructors -public: // methods - ConcreteBuilderT2() : - k_(name()) { + ConcreteBuilderT2() : key_(name()) { DEBUG_BUILDER("ConcreteBuilderT2() -- " << T::className()); - Factory::instance().regist(k_, builder_ptr(this)); + Factory::instance().regist(key_, this); } - ConcreteBuilderT2(const key_t& k) : - k_(k) { + explicit ConcreteBuilderT2(const typename base_t::key_t& k) : key_(k) { DEBUG_BUILDER("ConcreteBuilderT2() -- " << T::className()); - Factory::instance().regist(k_, builder_ptr(this)); + Factory::instance().regist(key_, this); } + ConcreteBuilderT2(const ConcreteBuilderT2&) = delete; + ConcreteBuilderT2(ConcreteBuilderT2&&) = delete; + + // -- Destructor + ~ConcreteBuilderT2() override { DEBUG_BUILDER("~ConcreteBuilderT2() -- " << T::className()); - Factory::instance().unregist(k_); +#if eckit_HAVE_ECKIT_MEMORY_FACTORY_EMPTY_DESTRUCTION + Factory::instance().unregist(key_); +#endif } + // -- Operators + + void operator=(const ConcreteBuilderT2&) = delete; + void operator=(ConcreteBuilderT2&&) = delete; + + // -- Overridden methods + typename base_t::key_t name() const override { return T::className(); } + typename base_t::product_t* create(typename base_t::ARG1 p1, typename base_t::ARG2 p2) const override { + return new T(p1, p2); + } - product_ptr create(ARG1 p1, ARG2 p2) const override { return new T(p1, p2); } private: - key_t k_; + // -- Members + + typename base_t::key_t key_; }; + #define register_BuilderT2(ABSTRACT, CONCRETE, NAME) \ static struct Register__##ABSTRACT##__##CONCRETE##__T2 { \ Register__##ABSTRACT##__##CONCRETE##__T2() { \ @@ -263,5 +310,3 @@ class ConcreteBuilderT2 : public BuilderT2 { //------------------------------------------------------------------------------------------------------ } // namespace eckit - -#endif // eckit_memory_Builder_h diff --git a/src/eckit/memory/Factory.h b/src/eckit/memory/Factory.h index 380fa5c05..87731b830 100644 --- a/src/eckit/memory/Factory.h +++ b/src/eckit/memory/Factory.h @@ -3,43 +3,56 @@ * * This software is licensed under the terms of the Apache Licence Version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation nor * does it submit to any jurisdiction. */ -#ifndef eckit_memory_Factory_h -#define eckit_memory_Factory_h - /// @file Factory.h /// @author Tiago Quintino +/// @author Pedro Maciel /// @date Jul 2014 + +#pragma once + #include #include #include +#include "eckit/eckit_config.h" #include "eckit/exception/Exceptions.h" #include "eckit/thread/AutoLock.h" #include "eckit/thread/Mutex.h" + namespace eckit { //------------------------------------------------------------------------------------------------------ template class Factory { +public: + // -- Types + + using product_t = T; + using builder_t = typename product_t::builder_t; + using key_t = std::string; + using storage_t = std::map; + + // -- Constructors -public: // types - typedef std::string key_t; + Factory(const Factory&) = delete; + Factory(Factory&&) = delete; - typedef T product_t; - typedef typename product_t::builder_t builder_t; - typedef builder_t* builder_ptr; + // -- Operators - typedef std::map storage_t; + void operator=(const Factory&) = delete; + void operator=(Factory&&) = delete; + + // -- Methods -public: // methods /// @return the instance of this singleton factory static Factory& instance(); @@ -48,12 +61,12 @@ class Factory { /// Checks if a builder is registered /// @param name of the builder - bool exists(const key_t& k) const; + bool exists(const key_t&) const; /// Registers a builder /// @param builder pointer /// @throw BadParameter if the builder already registered - void regist(const key_t&, builder_ptr); + void regist(const key_t&, builder_t*); /// Remove a registered builder /// @throw BadParameter if the builder is not registered @@ -61,39 +74,39 @@ class Factory { /// Gets the builder registered to the associated key /// @param name of the builder - const builder_t& get(const key_t& name) const; + const builder_t& get(const key_t&) const; /// @returns the number of builders registered to the factory size_t size() const; + /// @returns the builder keys registered to the factory std::vector keys() const; - friend std::ostream& operator<<(std::ostream& os, const Factory& o) { - o.print(os); - return os; - } - -private: // methods - void print(std::ostream&) const; +private: + // -- Constructors - Factory() { - // std::cout << "Building Factory of " << build_type() << std::endl; - } + Factory() = default; - ~Factory() { - // std::cout << "Destroying Factory of " << build_type() << std::endl; - // if( store_.size() != 0 ) - // { - // std::cout << "WARNING : Factory of " << build_type() << " still has " << size() << " providers" << - // std::endl; std::cout << *this << std::endl; - // } +#if eckit_HAVE_ECKIT_MEMORY_FACTORY_EMPTY_DESTRUCTION + // -- Destructor + ~Factory() { ASSERT(store_.empty()); } +#endif - ASSERT(store_.size() == 0); - } + // -- Members -private: // members mutable Mutex mutex_; ///< mutex protecting Factory singleton storage_t store_; ///< storage for the builders in a map indexed by key_t + + // -- Methods + + void print(std::ostream&) const; + + // -- Friends + + friend std::ostream& operator<<(std::ostream& os, const Factory& o) { + o.print(os); + return os; + } }; //------------------------------------------------------------------------------------------------------ @@ -111,11 +124,11 @@ bool Factory::exists(const key_t& k) const { } template -void Factory::regist(const key_t& k, builder_ptr b) { +void Factory::regist(const key_t& k, builder_t* b) { AutoLock lock(mutex_); - ASSERT(b); + ASSERT(b != nullptr); if (exists(k)) { - throw BadParameter("Factory of " + build_type() + " has already a builder for " + k, Here()); + throw BadParameter("Factory(" + build_type() + ") has already a builder for " + k, Here()); } store_[k] = b; } @@ -124,7 +137,7 @@ template void Factory::unregist(const key_t& k) { AutoLock lock(mutex_); if (!exists(k)) { - throw BadParameter("Factory of " + build_type() + " has no builder for " + k, Here()); + throw BadParameter("Factory(" + build_type() + ") has no builder for " + k, Here()); } store_.erase(k); } @@ -139,9 +152,9 @@ template const typename Factory::builder_t& Factory::get(const key_t& k) const { AutoLock lock(mutex_); if (!exists(k)) { - throw BadParameter("Factory of " + build_type() + " has no builder for " + k, Here()); + throw BadParameter("Factory(" + build_type() + ") has no builder for " + k, Here()); } - return *store_.find(k)->second; + return *(store_.find(k)->second); } template @@ -149,13 +162,13 @@ void Factory::print(std::ostream& os) const { AutoLock lock(mutex_); os << "Factory(" << build_type() << ")" << std::endl; - size_t key_width = 0; - for (typename storage_t::const_iterator i = store_.begin(); i != store_.end(); ++i) { - key_width = std::max(i->first.size(), key_width); + int key_width = 0; + for (const auto& i : store_) { + key_width = std::max(static_cast(i.first.size()), key_width); } - for (typename storage_t::const_iterator i = store_.begin(); i != store_.end(); ++i) { - os << " " << std::setw(key_width) << std::left << i->first << " -- " << (*(*i).second) << std::endl; + for (const auto& i : store_) { + os << " " << std::setw(key_width) << std::left << i.first << " -- " << i.second << std::endl; } } @@ -165,8 +178,8 @@ std::vector::key_t> Factory::keys() const { std::vector keysv; keysv.reserve(store_.size()); - for (typename storage_t::const_iterator i = store_.begin(); i != store_.end(); ++i) { - keysv.push_back(i->first); + for (const auto& i : store_) { + keysv.push_back(i.first); } return keysv; } @@ -174,5 +187,3 @@ std::vector::key_t> Factory::keys() const { //------------------------------------------------------------------------------------------------------ } // namespace eckit - -#endif // eckit_memory_Factory_h diff --git a/src/eckit/mpi/Comm.cc b/src/eckit/mpi/Comm.cc index eb88fa252..6071135fb 100644 --- a/src/eckit/mpi/Comm.cc +++ b/src/eckit/mpi/Comm.cc @@ -36,7 +36,7 @@ constexpr bool have_parallel() { class Environment { public: - static const char* getDefaultCommType() { + static std::string_view getDefaultCommType() { // Force a given communicator (only required if e.g. running serial applications with MPI) if (const char* forcedComm = ::getenv("ECKIT_MPI_FORCE")) { return forcedComm; @@ -80,10 +80,10 @@ class Environment { default_ = world; } - void setDefaut(const char* name) { + void setDefault(std::string_view name) { AutoLock lock(mutex_); - std::map::iterator itr = communicators.find(name); + auto itr = communicators.find(name); if (itr != communicators.end()) { default_ = itr->second; return; @@ -95,10 +95,10 @@ class Environment { for (itr = communicators.begin(); itr != communicators.end(); ++itr) { eckit::Log::error() << " " << (*itr).first << std::endl; } - throw eckit::SeriousBug(std::string("No communicator called ") + name, Here()); + throw eckit::SeriousBug("No communicator called " + std::string{name}, Here()); } - bool hasComm(const char* name) { + bool hasComm(std::string_view name) { AutoLock lock(mutex_); std::map::iterator itr = communicators.find(name); if (itr != communicators.end()) { @@ -120,17 +120,17 @@ class Environment { void finaliseAllComms() { AutoLock lock(mutex_); - std::map::iterator itr = communicators.begin(); + auto itr = communicators.begin(); for (; itr != communicators.end(); ++itr) { delete itr->second; } communicators.clear(); } - Comm& getComm(const char* name = nullptr) { + Comm& getComm(std::string_view name = {}) { AutoLock lock(mutex_); - if (!name && default_) { + if (name.empty() && default_) { return *default_; /* most common case first */ } @@ -138,12 +138,12 @@ class Environment { initDefault(); } - if (!name) { + if (name.empty()) { ASSERT(default_); /* sanity check init was successful */ return *default_; } - std::map::iterator itr = communicators.find(name); + auto itr = communicators.find(name); if (itr != communicators.end()) { return *itr->second; } @@ -153,30 +153,30 @@ class Environment { for (itr = communicators.begin(); itr != communicators.end(); ++itr) { eckit::Log::error() << " " << (*itr).first << std::endl; } - throw eckit::SeriousBug(std::string("No communicator called ") + name, Here()); + throw eckit::SeriousBug("No communicator called " + std::string{name}, Here()); } - void addComm(const char* name, int comm) { + void addComm(std::string_view name, int comm) { AutoLock lock(mutex_); if (hasComm(name)) { - throw SeriousBug("Communicator with name " + std::string(name) + " already exists", Here()); + throw SeriousBug("Communicator with name " + std::string{name} + " already exists", Here()); } Comm* pComm = CommFactory::build(name, getDefaultCommType(), comm); - communicators[name] = pComm; + communicators.emplace(name, pComm); } - void addComm(const char* name, Comm* comm) { + void addComm(std::string_view name, Comm* comm) { AutoLock lock(mutex_); if (hasComm(name)) { - throw SeriousBug("Communicator with name " + std::string(name) + " already exists", Here()); + throw SeriousBug("Communicator with name " + std::string{name} + " already exists", Here()); } - communicators[name] = comm; + communicators.emplace(name, comm); } - void deleteComm(const char* name) { + void deleteComm(std::string_view name) { AutoLock lock(mutex_); auto itr = communicators.find(name); @@ -188,7 +188,7 @@ class Environment { // refuse to delete the default communicator if (default_ == comm) { throw SeriousBug("Trying to delete the default Communicator with name " - + std::string(name), + + std::string{name}, Here()); } @@ -198,7 +198,7 @@ class Environment { communicators.erase(itr); } else { - throw SeriousBug("Communicator with name " + std::string(name) + " does not exist", Here()); + throw SeriousBug("Communicator with name " + std::string{name} + " does not exist", Here()); } } @@ -215,7 +215,7 @@ class Environment { Comm* default_; - std::map communicators; + std::map> communicators; eckit::Mutex mutex_; }; @@ -224,21 +224,21 @@ class Environment { class CommFactories { public: - void registFactory(const std::string& builder, CommFactory* f) { + void registFactory(std::string_view builder, CommFactory* f) { AutoLock lock(mutex_); ASSERT(factories.find(builder) == factories.end()); - factories[builder] = f; + factories.emplace(builder, f); } - void unregistFactory(const std::string& builder) { + void unregistFactory(std::string_view builder) { AutoLock lock(mutex_); - factories.erase(builder); + factories.erase(std::string{builder}); } - CommFactory& getFactory(const std::string& builder) { + CommFactory& getFactory(std::string_view builder) const { AutoLock lock(mutex_); - std::map::const_iterator j = factories.find(builder); + auto j = factories.find(builder); if (j != factories.end()) { return *(j->second); @@ -250,7 +250,7 @@ class CommFactories { eckit::Log::error() << " " << (*j).first << std::endl; } - throw eckit::SeriousBug(std::string("No CommFactory called ") + builder, Here()); + throw eckit::SeriousBug("No CommFactory called " + std::string{builder}, Here()); } static CommFactories& instance() { @@ -261,11 +261,11 @@ class CommFactories { private: CommFactories() {} - std::map factories; - eckit::Mutex mutex_; + std::map> factories; + mutable eckit::Mutex mutex_; }; -CommFactory::CommFactory(const std::string& builder) : +CommFactory::CommFactory(std::string_view builder) : builder_(builder) { CommFactories::instance().registFactory(builder, this); } @@ -274,43 +274,43 @@ CommFactory::~CommFactory() { CommFactories::instance().unregistFactory(builder_); } -Comm* CommFactory::build(const std::string& name, const std::string& builder) { +Comm* CommFactory::build(std::string_view name, std::string_view builder) { return CommFactories::instance().getFactory(builder).make(name); } -Comm* CommFactory::build(const std::string& name, const std::string& builder, int comm) { +Comm* CommFactory::build(std::string_view name, std::string_view builder, int comm) { return CommFactories::instance().getFactory(builder).make(name, comm); } //---------------------------------------------------------------------------------------------------------------------- -Comm::Comm(const std::string& name) : +Comm::Comm(std::string_view name) : name_(name) {} Comm::~Comm() {} //---------------------------------------------------------------------------------------------------------------------- -Comm& comm(const char* name) { +Comm& comm(std::string_view name) { return Environment::instance().getComm(name); } -void setCommDefault(const char* name) { - Environment::instance().setDefaut(name); +void setCommDefault(std::string_view name) { + Environment::instance().setDefault(name); } -void addComm(const char* name, int comm) { +void addComm(std::string_view name, int comm) { Environment::instance().addComm(name, comm); } -void addComm(const char* name, Comm* comm) { +void addComm(std::string_view name, Comm* comm) { Environment::instance().addComm(name, comm); } -void deleteComm(const char* name) { +void deleteComm(std::string_view name) { Environment::instance().deleteComm(name); } -bool hasComm(const char* name) { +bool hasComm(std::string_view name) { return Environment::instance().hasComm(name); } diff --git a/src/eckit/mpi/Comm.h b/src/eckit/mpi/Comm.h index 5150d00c3..4c21635fe 100644 --- a/src/eckit/mpi/Comm.h +++ b/src/eckit/mpi/Comm.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "eckit/filesystem/PathName.h" @@ -35,25 +36,25 @@ class Environment; /// @returns the communicator registered with associated name, or default communicator when NULL is /// passed -Comm& comm(const char* name = nullptr); +Comm& comm(std::string_view name = {}); Comm& self(); /// Set a communicator as default -void setCommDefault(const char* name); +void setCommDefault(std::string_view name); /// Register a communicator comming from Fortran code -void addComm(const char* name, int comm); +void addComm(std::string_view name, int comm); /// Register an existing communicator -void addComm(const char* name, Comm* comm); +void addComm(std::string_view name, Comm* comm); /// Unregister and delete specific comm /// @pre Comm is registered in the environment -void deleteComm(const char* name); +void deleteComm(std::string_view name); /// Check if a communicator is registered -bool hasComm(const char* name); +bool hasComm(std::string_view name); std::vector listComms(); @@ -78,7 +79,7 @@ class Comm : private eckit::NonCopyable { friend class Environment; public: // class methods - static Comm& comm(const char* name = nullptr); + static Comm& comm(std::string_view name = {}); public: // methods std::string name() const { return name_; } @@ -477,7 +478,7 @@ class Comm : private eckit::NonCopyable { } protected: // methods - Comm(const std::string& name); + Comm(std::string_view name); virtual ~Comm(); @@ -492,21 +493,21 @@ class CommFactory { friend class eckit::mpi::Environment; std::string builder_; - virtual Comm* make(const std::string& name) = 0; - virtual Comm* make(const std::string& name, int) = 0; + virtual Comm* make(std::string_view name) = 0; + virtual Comm* make(std::string_view name, int) = 0; protected: - CommFactory(const std::string& builder); + CommFactory(std::string_view builder); virtual ~CommFactory(); - static Comm* build(const std::string& name, const std::string& builder); - static Comm* build(const std::string& name, const std::string& builder, int); + static Comm* build(std::string_view name, std::string_view builder); + static Comm* build(std::string_view name, std::string_view builder, int); }; template class CommBuilder : public CommFactory { - Comm* make(const std::string& name) override { return new T(name); } - Comm* make(const std::string& name, int comm) override { return new T(name, comm); } + Comm* make(std::string_view name) override { return new T(name); } + Comm* make(std::string_view name, int comm) override { return new T(name, comm); } public: CommBuilder(const std::string& builder) : diff --git a/src/eckit/mpi/Group.cc b/src/eckit/mpi/Group.cc index 539488295..0d5ad91ed 100644 --- a/src/eckit/mpi/Group.cc +++ b/src/eckit/mpi/Group.cc @@ -32,7 +32,7 @@ class NullGroupContent : public GroupContent { virtual NullGroupContent* union_group(const GroupContent&) const { return new NullGroupContent(); } - virtual size_t size() const { return -1; } + virtual size_t size() const { return 0; } virtual int rank() const { return -1; } diff --git a/src/eckit/mpi/Parallel.cc b/src/eckit/mpi/Parallel.cc index 2479e4422..1fc1f7af7 100644 --- a/src/eckit/mpi/Parallel.cc +++ b/src/eckit/mpi/Parallel.cc @@ -10,7 +10,7 @@ #include "eckit/mpi/Parallel.h" -#include +#include #include #include #include @@ -55,7 +55,7 @@ class MPIError : public eckit::Exception { //---------------------------------------------------------------------------------------------------------------------- -void MPICall(int code, const char* mpifunc, const eckit::CodeLocation& loc) { +void MPICall(int code, std::string_view mpifunc, const eckit::CodeLocation& loc) { if (code != MPI_SUCCESS) { char error[10240]; @@ -169,7 +169,7 @@ size_t getSize(MPI_Comm comm) { //---------------------------------------------------------------------------------------------------------------------- -Parallel::Parallel(const std::string& name) : +Parallel::Parallel(std::string_view name) : Comm(name) /* don't use member initialisation list */ { pthread_once(&once, init); @@ -185,7 +185,7 @@ Parallel::Parallel(const std::string& name) : size_ = getSize(comm_); } -Parallel::Parallel(const std::string& name, MPI_Comm comm, bool) : +Parallel::Parallel(std::string_view name, MPI_Comm comm, bool) : Comm(name) /* don't use member initialisation list */ { pthread_once(&once, init); @@ -201,7 +201,7 @@ Parallel::Parallel(const std::string& name, MPI_Comm comm, bool) : size_ = getSize(comm_); } -Parallel::Parallel(const std::string& name, int comm) : +Parallel::Parallel(std::string_view name, int comm) : Comm(name) { pthread_once(&once, init); diff --git a/src/eckit/mpi/Parallel.h b/src/eckit/mpi/Parallel.h index f0abfae0e..0b8e99aae 100644 --- a/src/eckit/mpi/Parallel.h +++ b/src/eckit/mpi/Parallel.h @@ -29,9 +29,9 @@ class Parallel : public eckit::mpi::Comm { template friend class CommBuilder; - Parallel(const std::string& name); - Parallel(const std::string& name, MPI_Comm comm, bool); - Parallel(const std::string& name, int comm); + Parallel(std::string_view name); + Parallel(std::string_view name, MPI_Comm comm, bool); + Parallel(std::string_view name, int comm); ~Parallel() override; diff --git a/src/eckit/mpi/ParallelGroup.cc b/src/eckit/mpi/ParallelGroup.cc index 0d7feae24..4653ecbd3 100644 --- a/src/eckit/mpi/ParallelGroup.cc +++ b/src/eckit/mpi/ParallelGroup.cc @@ -8,6 +8,8 @@ * does it submit to any jurisdiction. */ +#include + #include "eckit/mpi/ParallelGroup.h" #include "eckit/log/CodeLocation.h" #include "eckit/mpi/Parallel.h" @@ -16,7 +18,7 @@ namespace eckit { namespace mpi { -void MPICall(int code, const char* mpifunc, const eckit::CodeLocation& loc); +void MPICall(int code, std::string_view mpifunc, const eckit::CodeLocation& loc); #define MPI_CALL(a) MPICall(a, #a, Here()) //---------------------------------------------------------------------------------------------------------------------- @@ -35,8 +37,7 @@ ParallelGroup::ParallelGroup(MPI_Group group) : group_(group) {} void ParallelGroup::print(std::ostream& os) const { - os << "ParallelGroup(" - << ")"; + os << "ParallelGroup()"; } int ParallelGroup::group() const { diff --git a/src/eckit/mpi/ParallelRequest.cc b/src/eckit/mpi/ParallelRequest.cc index 6710cd79e..67b1c0036 100644 --- a/src/eckit/mpi/ParallelRequest.cc +++ b/src/eckit/mpi/ParallelRequest.cc @@ -8,6 +8,8 @@ * does it submit to any jurisdiction. */ +#include + #include "eckit/mpi/ParallelRequest.h" #include "eckit/log/CodeLocation.h" #include "eckit/mpi/Parallel.h" @@ -16,7 +18,7 @@ namespace eckit { namespace mpi { -void MPICall(int code, const char* mpifunc, const eckit::CodeLocation& loc); +void MPICall(int code, std::string_view mpifunc, const eckit::CodeLocation& loc); #define MPI_CALL(a) MPICall(a, #a, Here()) //---------------------------------------------------------------------------------------------------------------------- diff --git a/src/eckit/mpi/Serial.cc b/src/eckit/mpi/Serial.cc index 923bf464a..0ecb87008 100644 --- a/src/eckit/mpi/Serial.cc +++ b/src/eckit/mpi/Serial.cc @@ -10,7 +10,7 @@ #include "eckit/mpi/Serial.h" -#include +#include #include #include @@ -134,13 +134,13 @@ class SerialRequestPool : private NonCopyable { //---------------------------------------------------------------------------------------------------------------------- -Serial::Serial(const std::string& name) : +Serial::Serial(std::string_view name) : Comm(name) { rank_ = 0; size_ = 1; } -Serial::Serial(const std::string& name, int) : +Serial::Serial(std::string_view name, int) : Comm(name) { rank_ = 0; size_ = 1; diff --git a/src/eckit/mpi/Serial.h b/src/eckit/mpi/Serial.h index 482fe60f2..b561b1531 100644 --- a/src/eckit/mpi/Serial.h +++ b/src/eckit/mpi/Serial.h @@ -30,8 +30,8 @@ class Serial : public eckit::mpi::Comm { template friend class CommBuilder; - Serial(const std::string& name); - Serial(const std::string& name, int); + Serial(std::string_view name); + Serial(std::string_view name, int); ~Serial() override; diff --git a/src/eckit/net/IPAddress.cc b/src/eckit/net/IPAddress.cc index f891bb888..108396318 100644 --- a/src/eckit/net/IPAddress.cc +++ b/src/eckit/net/IPAddress.cc @@ -17,7 +17,7 @@ #include #include #include -#include +#include namespace eckit::net { diff --git a/src/eckit/net/ProxiedTCPServer.cc b/src/eckit/net/ProxiedTCPServer.cc index ae8f47fee..e4a70474d 100644 --- a/src/eckit/net/ProxiedTCPServer.cc +++ b/src/eckit/net/ProxiedTCPServer.cc @@ -9,7 +9,7 @@ */ #include -#include +#include #include #include diff --git a/src/eckit/net/SocketOptions.cc b/src/eckit/net/SocketOptions.cc index 85338d2fa..530ecf170 100644 --- a/src/eckit/net/SocketOptions.cc +++ b/src/eckit/net/SocketOptions.cc @@ -9,6 +9,7 @@ */ #include +#include #include "eckit/config/Resource.h" #include "eckit/net/SocketOptions.h" @@ -16,13 +17,9 @@ namespace eckit::net { static void init(SocketOptions& opts) { - static std::string bindAddr = Resource("localBindingAddress", ""); /* "127.0.0.1" */ - - opts.bindAddress(bindAddr); - - static int ListenBacklog = eckit::Resource("socketOptionsListenBacklog", 5); - opts.listenBacklog(ListenBacklog); + static std::string bindAddr = Resource("localBindingAddress", ""); /* "127.0.0.1" */ + static int ListenBacklog = eckit::Resource("socketOptionsListenBacklog", SOMAXCONN); static bool reusePort = eckit::Resource("socketOptionsReusePort", false); static bool reuseAddr = eckit::Resource("socketOptionsReuseAddr", false); @@ -34,6 +31,8 @@ static void init(SocketOptions& opts) { static int receiveBufferSize = eckit::Resource("socketOptionsReceiveBufferSize", 0); static int sendBufferSize = eckit::Resource("socketOptionsSendBufferSize", 0); + opts.bindAddress(bindAddr); + opts.listenBacklog(ListenBacklog); opts.reusePort(reusePort); opts.reuseAddr(reuseAddr); opts.noLinger(noLinger); diff --git a/src/eckit/net/TCPServer.cc b/src/eckit/net/TCPServer.cc index 425709dbe..3fc4cb120 100644 --- a/src/eckit/net/TCPServer.cc +++ b/src/eckit/net/TCPServer.cc @@ -9,7 +9,7 @@ */ #include -#include +#include #include #include diff --git a/src/eckit/net/TCPSocket.cc b/src/eckit/net/TCPSocket.cc index b75746648..8815fbbd7 100644 --- a/src/eckit/net/TCPSocket.cc +++ b/src/eckit/net/TCPSocket.cc @@ -8,6 +8,11 @@ * does it submit to any jurisdiction. */ +// Disable warnings: warning #550-D: variable "__d0" was set but never used [set_but_not_used] in FD_ZERO(&r); +#if defined(__NVCOMPILER) +#pragma diag_suppress 550 +#endif + #include // FreeBSD: must appear before netinet/ip.h #include @@ -16,7 +21,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/eckit/net/UDPClient.cc b/src/eckit/net/UDPClient.cc index b72db5529..9759240c6 100644 --- a/src/eckit/net/UDPClient.cc +++ b/src/eckit/net/UDPClient.cc @@ -9,10 +9,10 @@ */ #include -#include +#include #include #include -#include +#include #include #include #include diff --git a/src/eckit/net/UDPServer.cc b/src/eckit/net/UDPServer.cc index e221b91d1..724682b75 100644 --- a/src/eckit/net/UDPServer.cc +++ b/src/eckit/net/UDPServer.cc @@ -9,10 +9,10 @@ */ #include -#include +#include #include #include -#include +#include #include #include #include diff --git a/src/eckit/option/CmdArgs.cc b/src/eckit/option/CmdArgs.cc index 6769a788e..26d77a1e0 100644 --- a/src/eckit/option/CmdArgs.cc +++ b/src/eckit/option/CmdArgs.cc @@ -44,17 +44,6 @@ CmdArgs::CmdArgs(std::function usage, std::vector split_at(const std::string& s, char separator) { - if (auto found = s.find_first_of(separator); found != std::string::npos) { - return {s.substr(0, found), s.substr(found + 1)}; - } - return {s}; -} - -} // namespace - void CmdArgs::init(std::function usage, int args_count, int minimum_args, bool throw_on_error) { const Main& ctx = Main::instance(); diff --git a/src/eckit/option/EckitTool.cc b/src/eckit/option/EckitTool.cc index 0bab88329..69146f7c7 100644 --- a/src/eckit/option/EckitTool.cc +++ b/src/eckit/option/EckitTool.cc @@ -10,46 +10,35 @@ #include "EckitTool.h" -#include "eckit/log/Log.h" +#include "eckit/exception/Exceptions.h" #include "eckit/option/CmdArgs.h" namespace eckit { //---------------------------------------------------------------------------------------------------------------------- -static EckitTool* instance_ = 0; +static EckitTool* INSTANCE = nullptr; -EckitTool::EckitTool(int argc, char** argv) : - eckit::Tool(argc, argv, "ECKIT_HOME") { - ASSERT(instance_ == 0); - instance_ = this; +static void usage(const std::string& tool) { + ASSERT(INSTANCE != nullptr); + INSTANCE->usage(tool); } -static void usage(const std::string& tool) { - ASSERT(instance_); - instance_->usage(tool); +EckitTool::EckitTool(int argc, char** argv) : Tool(argc, argv, "ECKIT_HOME") { + ASSERT(INSTANCE == nullptr); + INSTANCE = this; } void EckitTool::run() { - - eckit::option::CmdArgs args(&eckit::usage, - options_, - numberOfPositionalArguments(), - minimumPositionalArguments()); + option::CmdArgs args(&eckit::usage, options_, numberOfPositionalArguments(), minimumPositionalArguments()); init(args); execute(args); finish(args); } -void EckitTool::usage(const std::string& tool) const { -} +void EckitTool::usage(const std::string& tool) const {} -void EckitTool::init(const eckit::option::CmdArgs& args) { -} - -void EckitTool::finish(const eckit::option::CmdArgs& args) { -} //---------------------------------------------------------------------------------------------------------------------- diff --git a/src/eckit/option/EckitTool.h b/src/eckit/option/EckitTool.h index d40b8fb59..3d4f5a0f1 100644 --- a/src/eckit/option/EckitTool.h +++ b/src/eckit/option/EckitTool.h @@ -10,12 +10,12 @@ /// @author Baudouin Raoult /// @author Tiago Quintino +/// @author Pedro Maciel /// @date Mar 2016 #pragma once -#include "eckit/filesystem/PathName.h" -#include "eckit/option/SimpleOption.h" +#include "eckit/option/Option.h" #include "eckit/runtime/Tool.h" namespace eckit { @@ -27,7 +27,7 @@ class CmdArgs; //---------------------------------------------------------------------------------------------------------------------- -class EckitTool : public eckit::Tool { +class EckitTool : public Tool { protected: // methods EckitTool(int argc, char** argv); @@ -39,14 +39,14 @@ class EckitTool : public eckit::Tool { std::vector options_; private: // methods - virtual void init(const eckit::option::CmdArgs& args); - virtual void execute(const eckit::option::CmdArgs& args) = 0; - virtual void finish(const eckit::option::CmdArgs& args); + virtual void init(const option::CmdArgs&) {} + virtual void execute(const option::CmdArgs&) = 0; + virtual void finish(const option::CmdArgs&) {} virtual int numberOfPositionalArguments() const { return -1; } virtual int minimumPositionalArguments() const { return -1; } - virtual void run(); + void run() override; }; //---------------------------------------------------------------------------------------------------------------------- diff --git a/src/eckit/os/AutoAlarm.cc b/src/eckit/os/AutoAlarm.cc index ce177b034..67bdd1a92 100644 --- a/src/eckit/os/AutoAlarm.cc +++ b/src/eckit/os/AutoAlarm.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include #include "eckit/exception/Exceptions.h" diff --git a/src/eckit/os/SemLocker.cc b/src/eckit/os/SemLocker.cc index 1e3503693..dbcce6d7a 100644 --- a/src/eckit/os/SemLocker.cc +++ b/src/eckit/os/SemLocker.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include #include #include diff --git a/src/eckit/os/Semaphore.cc b/src/eckit/os/Semaphore.cc index 2ea4331b6..ce657e16a 100644 --- a/src/eckit/os/Semaphore.cc +++ b/src/eckit/os/Semaphore.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include #include diff --git a/src/eckit/os/SignalHandler.h b/src/eckit/os/SignalHandler.h index 2c6bf226b..09b30664c 100644 --- a/src/eckit/os/SignalHandler.h +++ b/src/eckit/os/SignalHandler.h @@ -12,7 +12,7 @@ #define eckit_SignalHandler_h #include -#include +#include #include "eckit/eckit.h" diff --git a/src/eckit/parser/JSON.h b/src/eckit/parser/JSON.h index 814648d1e..6e9bbe41d 100644 --- a/src/eckit/parser/JSON.h +++ b/src/eckit/parser/JSON.h @@ -8,5 +8,5 @@ * does it submit to any jurisdiction. */ -#warning "Header eckit/log/JSON.h is deprecated -- include eckit/log/JSON.h" +#warning "Header eckit/parser/JSON.h is deprecated -- include eckit/log/JSON.h" #include "eckit/log/JSON.h" diff --git a/src/eckit/parser/YAMLParser.cc b/src/eckit/parser/YAMLParser.cc index 1e270ab1b..b54ae01e9 100644 --- a/src/eckit/parser/YAMLParser.cc +++ b/src/eckit/parser/YAMLParser.cc @@ -462,6 +462,7 @@ static Value toValue(std::string& s) { s.erase(remove(s.begin(), s.end(), '_'), s.end()); return Value(strtol(s.substr(2).c_str(), 0, 16)); } + [[fallthrough]]; case '1': case '2': @@ -478,6 +479,7 @@ static Value toValue(std::string& s) { long long d = Translator()(s); return Value(d); } + [[fallthrough]]; case '.': diff --git a/src/eckit/runtime/Library.cc b/src/eckit/runtime/Library.cc index 448758c6d..c993e24d2 100644 --- a/src/eckit/runtime/Library.cc +++ b/src/eckit/runtime/Library.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -// #include +// #include #include "eckit/runtime/Library.h" #include "eckit/exception/Exceptions.h" diff --git a/src/eckit/runtime/Monitor.cc b/src/eckit/runtime/Monitor.cc index 2b4994e34..fa146c011 100644 --- a/src/eckit/runtime/Monitor.cc +++ b/src/eckit/runtime/Monitor.cc @@ -9,7 +9,7 @@ */ #include -#include +#include #include "eckit/config/Resource.h" #include "eckit/container/MappedArray.h" diff --git a/src/eckit/runtime/PipeHandler.cc b/src/eckit/runtime/PipeHandler.cc index b53ec61b9..b32250104 100644 --- a/src/eckit/runtime/PipeHandler.cc +++ b/src/eckit/runtime/PipeHandler.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include "eckit/config/LibEcKit.h" #include "eckit/log/Log.h" diff --git a/src/eckit/runtime/ProcessControler.cc b/src/eckit/runtime/ProcessControler.cc index f7a66e729..62c825ad2 100644 --- a/src/eckit/runtime/ProcessControler.cc +++ b/src/eckit/runtime/ProcessControler.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include #include diff --git a/src/eckit/runtime/TaskInfo.cc b/src/eckit/runtime/TaskInfo.cc index c8e5bc5b0..e5a70bf22 100644 --- a/src/eckit/runtime/TaskInfo.cc +++ b/src/eckit/runtime/TaskInfo.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include #include diff --git a/src/eckit/serialisation/PipeStream.cc b/src/eckit/serialisation/PipeStream.cc index cace9f97b..9990b8d68 100644 --- a/src/eckit/serialisation/PipeStream.cc +++ b/src/eckit/serialisation/PipeStream.cc @@ -9,7 +9,7 @@ */ #include -#include +#include #include #include "eckit/exception/Exceptions.h" diff --git a/src/eckit/serialisation/Stream.cc b/src/eckit/serialisation/Stream.cc index 0f8d7db4c..7a84b89a6 100644 --- a/src/eckit/serialisation/Stream.cc +++ b/src/eckit/serialisation/Stream.cc @@ -11,7 +11,7 @@ #include "eckit/serialisation/Stream.h" #include -#include +#include #include #include diff --git a/src/eckit/sql/CMakeLists.txt b/src/eckit/sql/CMakeLists.txt index ea2739ea8..adeb9d7ec 100644 --- a/src/eckit/sql/CMakeLists.txt +++ b/src/eckit/sql/CMakeLists.txt @@ -178,6 +178,6 @@ ecbuild_add_library( TARGET eckit_sql TYPE SHARED expression/ShiftedColumnExpression.cc PUBLIC_LIBS eckit ) -# Disable all warnings for PGI compiler as it is overzealous and cherry-picking is not possible -target_compile_options( eckit_sql PRIVATE $<$:-w> ) +# Disable all warnings for PGI/NVHPC compiler as it is overzealous and cherry-picking is not possible +target_compile_options( eckit_sql PRIVATE $<$:-w> $<$:-w> ) diff --git a/src/eckit/sql/SQLTypedefs.h b/src/eckit/sql/SQLTypedefs.h index f709102ee..108368919 100644 --- a/src/eckit/sql/SQLTypedefs.h +++ b/src/eckit/sql/SQLTypedefs.h @@ -11,7 +11,7 @@ #ifndef eckit_sql_SQLTypes_h #define eckit_sql_SQLTypes_h -#include +#include #include #include #include diff --git a/src/eckit/sql/expression/BitColumnExpression.h b/src/eckit/sql/expression/BitColumnExpression.h index e0a901921..9434a6c61 100644 --- a/src/eckit/sql/expression/BitColumnExpression.h +++ b/src/eckit/sql/expression/BitColumnExpression.h @@ -45,6 +45,12 @@ class BitColumnExpression : public ColumnExpression { // -- Overridden methods void prepare(SQLSelect& sql) override; void updateType(SQLSelect& sql) override; + + // Use SQLExpression's eval rather than ColumnExpression's + void eval(double* out, bool& missing) const override { + SQLExpression::eval(out, missing); + } + double eval(bool& missing) const override; virtual void expandStars(const std::vector>&, expression::Expressions&) override; diff --git a/src/eckit/sql/expression/ConstantExpression.h b/src/eckit/sql/expression/ConstantExpression.h index 53ce33770..db1aedd20 100644 --- a/src/eckit/sql/expression/ConstantExpression.h +++ b/src/eckit/sql/expression/ConstantExpression.h @@ -43,6 +43,7 @@ class ConstantExpression : public SQLExpression { void cleanup(SQLSelect&) override { NOTIMP; } // -- For WHERE + using SQLExpression::eval; double eval(bool& missing) const override { missing = missing_; return value_; diff --git a/src/eckit/sql/expression/NumberExpression.cc b/src/eckit/sql/expression/NumberExpression.cc index b17cdbd8c..4470aa4bb 100644 --- a/src/eckit/sql/expression/NumberExpression.cc +++ b/src/eckit/sql/expression/NumberExpression.cc @@ -17,9 +17,11 @@ namespace eckit::sql::expression { //---------------------------------------------------------------------------------------------------------------------- NumberExpression::NumberExpression(double value) : + SQLExpression(), value_(value) {} NumberExpression::NumberExpression(const NumberExpression& other) : + SQLExpression(), value_(other.value_) {} std::shared_ptr NumberExpression::clone() const { diff --git a/src/eckit/sql/expression/NumberExpression.h b/src/eckit/sql/expression/NumberExpression.h index 5b5cd3044..eaae735be 100644 --- a/src/eckit/sql/expression/NumberExpression.h +++ b/src/eckit/sql/expression/NumberExpression.h @@ -44,6 +44,7 @@ class NumberExpression : public SQLExpression { void cleanup(SQLSelect& sql) override; const type::SQLType* type() const override; + using SQLExpression::eval; double eval(bool& missing) const override; bool isConstant() const override { return true; } bool isNumber() const override { return true; } diff --git a/src/eckit/sql/expression/ParameterExpression.cc b/src/eckit/sql/expression/ParameterExpression.cc index 132c4df1d..8d0f8d073 100644 --- a/src/eckit/sql/expression/ParameterExpression.cc +++ b/src/eckit/sql/expression/ParameterExpression.cc @@ -18,12 +18,14 @@ namespace eckit::sql::expression { //---------------------------------------------------------------------------------------------------------------------- ParameterExpression::ParameterExpression(int which) : + SQLExpression(), value_(0), which_(which) { // don't use any Log::* here // std::cout << "new ParameterExpression " << name << std::endl; } ParameterExpression::ParameterExpression(const ParameterExpression& other) : + SQLExpression(), value_(other.value_), which_(other.which_) {} diff --git a/src/eckit/sql/expression/ParameterExpression.h b/src/eckit/sql/expression/ParameterExpression.h index 116aaea17..c9363e721 100644 --- a/src/eckit/sql/expression/ParameterExpression.h +++ b/src/eckit/sql/expression/ParameterExpression.h @@ -43,6 +43,7 @@ class ParameterExpression : public SQLExpression { void prepare(SQLSelect& sql) override; void cleanup(SQLSelect& sql) override; + using SQLExpression::eval; double eval(bool& missing) const override; const type::SQLType* type() const override; bool isConstant() const override; diff --git a/src/eckit/sql/expression/ShiftedColumnExpression.h b/src/eckit/sql/expression/ShiftedColumnExpression.h index 3ecb873bd..c8fb0d11d 100644 --- a/src/eckit/sql/expression/ShiftedColumnExpression.h +++ b/src/eckit/sql/expression/ShiftedColumnExpression.h @@ -65,6 +65,7 @@ class ShiftedColumnExpression : public T { // -- Overridden methods void print(std::ostream& s) const override; void cleanup(SQLSelect& sql) override; + using T::eval; double eval(bool& missing) const override; void output(SQLOutput& s) const override; diff --git a/src/eckit/sql/expression/function/DoubleFunctions.cc b/src/eckit/sql/expression/function/DoubleFunctions.cc index f50668db8..986e6e147 100644 --- a/src/eckit/sql/expression/function/DoubleFunctions.cc +++ b/src/eckit/sql/expression/function/DoubleFunctions.cc @@ -11,7 +11,7 @@ #include "eckit/sql/expression/function/FunctionFactory.h" -#include +#include #include #include @@ -32,6 +32,7 @@ class ArityFunction : public FunctionExpression { template class UnaryFunction : public ArityFunction, 1> { + using FunctionExpression::eval; double eval(bool& m) const { double a0 = this->args_[0]->eval(m); @@ -49,6 +50,7 @@ class UnaryFunction : public ArityFunction, 1> { template class BinaryFunction : public ArityFunction, 2> { + using FunctionExpression::eval; double eval(bool& m) const { double a0 = this->args_[0]->eval(m); @@ -70,6 +72,7 @@ class BinaryFunction : public ArityFunction, 2> { template class TertiaryFunction : public ArityFunction, 3> { + using FunctionExpression::eval; double eval(bool& m) const { double a0 = this->args_[0]->eval(m); @@ -95,6 +98,7 @@ class TertiaryFunction : public ArityFunction, 3> { template class QuaternaryFunction : public ArityFunction, 4> { + using FunctionExpression::eval; double eval(bool& m) const { double a0 = this->args_[0]->eval(m); @@ -124,6 +128,7 @@ class QuaternaryFunction : public ArityFunction, 4> { template class QuinaryFunction : public ArityFunction, 5> { + using FunctionExpression::eval; double eval(bool& m) const { double a0 = this->args_[0]->eval(m); @@ -364,6 +369,7 @@ double ldexp_double(double l, double r) { class MultiplyFunction : public ArityFunction { + using FunctionExpression::eval; double eval(bool& m) const { bool m0 = false; diff --git a/src/eckit/sql/expression/function/FunctionAND.h b/src/eckit/sql/expression/function/FunctionAND.h index 699987068..ee1642c42 100644 --- a/src/eckit/sql/expression/function/FunctionAND.h +++ b/src/eckit/sql/expression/function/FunctionAND.h @@ -38,6 +38,7 @@ class FunctionAND : public FunctionExpression { FunctionAND& operator=(const FunctionAND&); const eckit::sql::type::SQLType* type() const override; + using FunctionExpression::eval; double eval(bool& missing) const override; std::shared_ptr simplify(bool&) override; bool andSplit(expression::Expressions&) override; diff --git a/src/eckit/sql/expression/function/FunctionAVG.h b/src/eckit/sql/expression/function/FunctionAVG.h index 86e7933db..a874eb89c 100644 --- a/src/eckit/sql/expression/function/FunctionAVG.h +++ b/src/eckit/sql/expression/function/FunctionAVG.h @@ -42,6 +42,7 @@ class FunctionAVG : public FunctionExpression { void prepare(SQLSelect&) override; void cleanup(SQLSelect&) override; void partialResult() override; + using FunctionExpression::eval; double eval(bool& missing) const override; bool isAggregate() const override { return true; } diff --git a/src/eckit/sql/expression/function/FunctionCOUNT.h b/src/eckit/sql/expression/function/FunctionCOUNT.h index e236d2994..972770620 100644 --- a/src/eckit/sql/expression/function/FunctionCOUNT.h +++ b/src/eckit/sql/expression/function/FunctionCOUNT.h @@ -40,6 +40,7 @@ class FunctionCOUNT : public FunctionExpression { void prepare(SQLSelect&) override; void cleanup(SQLSelect&) override; void partialResult() override; + using FunctionExpression::eval; double eval(bool& missing) const override; bool isAggregate() const override { return true; } diff --git a/src/eckit/sql/expression/function/FunctionDOTP.h b/src/eckit/sql/expression/function/FunctionDOTP.h index 967ba5f6f..7cf52a898 100644 --- a/src/eckit/sql/expression/function/FunctionDOTP.h +++ b/src/eckit/sql/expression/function/FunctionDOTP.h @@ -39,6 +39,7 @@ class FunctionDOTP : public FunctionExpression { void prepare(SQLSelect&) override; void cleanup(SQLSelect&) override; void partialResult() override; + using FunctionExpression::eval; double eval(bool& missing) const override; bool isAggregate() const override { return true; } diff --git a/src/eckit/sql/expression/function/FunctionEQ.h b/src/eckit/sql/expression/function/FunctionEQ.h index 0cbb5dafd..2526e9347 100644 --- a/src/eckit/sql/expression/function/FunctionEQ.h +++ b/src/eckit/sql/expression/function/FunctionEQ.h @@ -39,6 +39,7 @@ class FunctionEQ : public FunctionExpression { // -- Overridden methods const eckit::sql::type::SQLType* type() const override; + using FunctionExpression::eval; double eval(bool& missing) const override; std::shared_ptr simplify(bool&) override; diff --git a/src/eckit/sql/expression/function/FunctionFIRST.h b/src/eckit/sql/expression/function/FunctionFIRST.h index 1d8fac34f..b4c21671d 100644 --- a/src/eckit/sql/expression/function/FunctionFIRST.h +++ b/src/eckit/sql/expression/function/FunctionFIRST.h @@ -40,6 +40,7 @@ class FunctionFIRST : public FunctionExpression { void prepare(SQLSelect&) override; void cleanup(SQLSelect&) override; void partialResult() override; + using FunctionExpression::eval; double eval(bool& missing) const override; bool isAggregate() const override { return true; } diff --git a/src/eckit/sql/expression/function/FunctionIN.h b/src/eckit/sql/expression/function/FunctionIN.h index 122a514cc..9ec6afd94 100644 --- a/src/eckit/sql/expression/function/FunctionIN.h +++ b/src/eckit/sql/expression/function/FunctionIN.h @@ -35,6 +35,7 @@ class FunctionIN : public FunctionExpression { size_t size_; const eckit::sql::type::SQLType* type() const override; + using FunctionExpression::eval; double eval(bool& missing) const override; // -- Friends diff --git a/src/eckit/sql/expression/function/FunctionIntegerExpression.cc b/src/eckit/sql/expression/function/FunctionIntegerExpression.cc index d9a38ad44..d4054cd3b 100644 --- a/src/eckit/sql/expression/function/FunctionIntegerExpression.cc +++ b/src/eckit/sql/expression/function/FunctionIntegerExpression.cc @@ -52,6 +52,7 @@ class MathFunctionIntegerExpression_1 : public FunctionIntegerExpression { } private: // methods + using FunctionIntegerExpression::eval; double eval(bool& m) const { bool missing = false; double v = args_[0]->eval(missing); diff --git a/src/eckit/sql/expression/function/FunctionJOIN.h b/src/eckit/sql/expression/function/FunctionJOIN.h index 7087d5147..09aa028ba 100644 --- a/src/eckit/sql/expression/function/FunctionJOIN.h +++ b/src/eckit/sql/expression/function/FunctionJOIN.h @@ -34,6 +34,7 @@ class FunctionJOIN : public FunctionExpression { // -- Overridden methods const eckit::sql::type::SQLType* type() const override; + using FunctionExpression::eval; double eval(bool& missing) const override; // -- Friends diff --git a/src/eckit/sql/expression/function/FunctionJULIAN.h b/src/eckit/sql/expression/function/FunctionJULIAN.h index bde591328..a1bfb4642 100644 --- a/src/eckit/sql/expression/function/FunctionJULIAN.h +++ b/src/eckit/sql/expression/function/FunctionJULIAN.h @@ -34,6 +34,7 @@ class FunctionJULIAN : public FunctionExpression { // -- Overridden methods const eckit::sql::type::SQLType* type() const override; + using FunctionExpression::eval; double eval(bool& missing) const override; // -- Friends diff --git a/src/eckit/sql/expression/function/FunctionJULIAN_SECONDS.h b/src/eckit/sql/expression/function/FunctionJULIAN_SECONDS.h index e03c61867..bb312957c 100644 --- a/src/eckit/sql/expression/function/FunctionJULIAN_SECONDS.h +++ b/src/eckit/sql/expression/function/FunctionJULIAN_SECONDS.h @@ -35,6 +35,7 @@ class FunctionJULIAN_SECONDS : public FunctionExpression { // -- Overridden methods const eckit::sql::type::SQLType* type() const override; + using FunctionExpression::eval; double eval(bool& missing) const override; // -- Friends diff --git a/src/eckit/sql/expression/function/FunctionLAST.h b/src/eckit/sql/expression/function/FunctionLAST.h index af1ee5609..a7ff57310 100644 --- a/src/eckit/sql/expression/function/FunctionLAST.h +++ b/src/eckit/sql/expression/function/FunctionLAST.h @@ -40,6 +40,7 @@ class FunctionLAST : public FunctionExpression { void prepare(SQLSelect&) override; void cleanup(SQLSelect&) override; void partialResult() override; + using FunctionExpression::eval; double eval(bool& missing) const override; bool isAggregate() const override { return true; } diff --git a/src/eckit/sql/expression/function/FunctionMAX.h b/src/eckit/sql/expression/function/FunctionMAX.h index 467c56cca..23ead6bc1 100644 --- a/src/eckit/sql/expression/function/FunctionMAX.h +++ b/src/eckit/sql/expression/function/FunctionMAX.h @@ -39,6 +39,7 @@ class FunctionMAX : public FunctionExpression { void prepare(SQLSelect&) override; void cleanup(SQLSelect&) override; void partialResult() override; + using FunctionExpression::eval; double eval(bool& missing) const override; bool isAggregate() const override { return true; } diff --git a/src/eckit/sql/expression/function/FunctionMIN.h b/src/eckit/sql/expression/function/FunctionMIN.h index 666caa895..e1acfbd27 100644 --- a/src/eckit/sql/expression/function/FunctionMIN.h +++ b/src/eckit/sql/expression/function/FunctionMIN.h @@ -39,6 +39,7 @@ class FunctionMIN : public FunctionExpression { void prepare(SQLSelect&) override; void cleanup(SQLSelect&) override; void partialResult() override; + using FunctionExpression::eval; double eval(bool& missing) const override; bool isAggregate() const override { return true; } diff --git a/src/eckit/sql/expression/function/FunctionNE.h b/src/eckit/sql/expression/function/FunctionNE.h index f17852b1d..1b6e6c7fe 100644 --- a/src/eckit/sql/expression/function/FunctionNE.h +++ b/src/eckit/sql/expression/function/FunctionNE.h @@ -38,6 +38,7 @@ class FunctionNE : public FunctionExpression { // -- Overridden methods const eckit::sql::type::SQLType* type() const override; + using FunctionExpression::eval; double eval(bool& missing) const override; // -- Friends diff --git a/src/eckit/sql/expression/function/FunctionNORM.h b/src/eckit/sql/expression/function/FunctionNORM.h index 87b7fa3eb..10ea16c54 100644 --- a/src/eckit/sql/expression/function/FunctionNORM.h +++ b/src/eckit/sql/expression/function/FunctionNORM.h @@ -39,6 +39,7 @@ class FunctionNORM : public FunctionExpression { void prepare(SQLSelect&) override; void cleanup(SQLSelect&) override; void partialResult() override; + using FunctionExpression::eval; double eval(bool& missing) const override; bool isAggregate() const override { return true; } diff --git a/src/eckit/sql/expression/function/FunctionNOT_IN.h b/src/eckit/sql/expression/function/FunctionNOT_IN.h index b251b84ef..56827c55f 100644 --- a/src/eckit/sql/expression/function/FunctionNOT_IN.h +++ b/src/eckit/sql/expression/function/FunctionNOT_IN.h @@ -36,6 +36,7 @@ class FunctionNOT_IN : public FunctionExpression { // -- Overridden methods const eckit::sql::type::SQLType* type() const override; + using FunctionExpression::eval; double eval(bool& missing) const override; // -- Friends diff --git a/src/eckit/sql/expression/function/FunctionNOT_NULL.h b/src/eckit/sql/expression/function/FunctionNOT_NULL.h index ddd40f96a..d3cfd8e3e 100644 --- a/src/eckit/sql/expression/function/FunctionNOT_NULL.h +++ b/src/eckit/sql/expression/function/FunctionNOT_NULL.h @@ -34,6 +34,7 @@ class FunctionNOT_NULL : public FunctionExpression { FunctionNOT_NULL& operator=(const FunctionNOT_NULL&); // -- Overridden methods + using FunctionExpression::eval; double eval(bool& missing) const override; // -- Friends diff --git a/src/eckit/sql/expression/function/FunctionNULL.h b/src/eckit/sql/expression/function/FunctionNULL.h index fa4359a2d..a371ccd32 100644 --- a/src/eckit/sql/expression/function/FunctionNULL.h +++ b/src/eckit/sql/expression/function/FunctionNULL.h @@ -34,6 +34,7 @@ class FunctionNULL : public FunctionExpression { FunctionNULL& operator=(const FunctionNULL&); // -- Overridden methods + using FunctionExpression::eval; double eval(bool& missing) const override; // -- Friends // friend std::ostream& operator<<(std::ostream& s,const FunctionNULL& p) diff --git a/src/eckit/sql/expression/function/FunctionNVL.h b/src/eckit/sql/expression/function/FunctionNVL.h index 8f2c15f06..d2f5cb961 100644 --- a/src/eckit/sql/expression/function/FunctionNVL.h +++ b/src/eckit/sql/expression/function/FunctionNVL.h @@ -34,6 +34,7 @@ class FunctionNVL : public FunctionExpression { // -- Overridden methods const eckit::sql::type::SQLType* type() const override; + using FunctionExpression::eval; double eval(bool& missing) const override; // -- Friends diff --git a/src/eckit/sql/expression/function/FunctionOR.h b/src/eckit/sql/expression/function/FunctionOR.h index eda503ce7..8b35e46d4 100644 --- a/src/eckit/sql/expression/function/FunctionOR.h +++ b/src/eckit/sql/expression/function/FunctionOR.h @@ -27,6 +27,7 @@ class FunctionOR : public FunctionExpression { // -- Overridden methods std::shared_ptr clone() const override; + using FunctionExpression::eval; double eval(bool& missing) const override; const eckit::sql::type::SQLType* type() const override; std::shared_ptr simplify(bool&) override; diff --git a/src/eckit/sql/expression/function/FunctionRLIKE.h b/src/eckit/sql/expression/function/FunctionRLIKE.h index 9bd342181..f14305691 100644 --- a/src/eckit/sql/expression/function/FunctionRLIKE.h +++ b/src/eckit/sql/expression/function/FunctionRLIKE.h @@ -43,6 +43,7 @@ class FunctionRLIKE : public FunctionExpression { // -- Overridden methods const eckit::sql::type::SQLType* type() const override; + using FunctionExpression::eval; double eval(bool& missing) const override; // -- Friends diff --git a/src/eckit/sql/expression/function/FunctionRMS.h b/src/eckit/sql/expression/function/FunctionRMS.h index bf52dc912..282400f16 100644 --- a/src/eckit/sql/expression/function/FunctionRMS.h +++ b/src/eckit/sql/expression/function/FunctionRMS.h @@ -25,6 +25,7 @@ class FunctionRMS : public FunctionExpression { ~FunctionRMS(); // -- Overridden methods + using FunctionExpression::eval; double eval(bool& missing) const override; std::shared_ptr clone() const override; diff --git a/src/eckit/sql/expression/function/FunctionROWNUMBER.h b/src/eckit/sql/expression/function/FunctionROWNUMBER.h index a766ac1f7..39f89a34e 100644 --- a/src/eckit/sql/expression/function/FunctionROWNUMBER.h +++ b/src/eckit/sql/expression/function/FunctionROWNUMBER.h @@ -38,6 +38,7 @@ class FunctionROWNUMBER : public FunctionIntegerExpression { void cleanup(SQLSelect&) override; bool isConstant() const override; void partialResult() override; + using FunctionIntegerExpression::eval; double eval(bool& missing) const override; std::shared_ptr simplify(bool&) override; bool isAggregate() const override { return false; } diff --git a/src/eckit/sql/expression/function/FunctionSTDEV.h b/src/eckit/sql/expression/function/FunctionSTDEV.h index ba7a47f04..9f1c1a74b 100644 --- a/src/eckit/sql/expression/function/FunctionSTDEV.h +++ b/src/eckit/sql/expression/function/FunctionSTDEV.h @@ -30,6 +30,7 @@ class FunctionSTDEV : public FunctionVAR { // No copy allowed FunctionSTDEV& operator=(const FunctionSTDEV&); + using FunctionVAR::eval; double eval(bool& missing) const override; const eckit::sql::type::SQLType* type() const override; diff --git a/src/eckit/sql/expression/function/FunctionSUM.h b/src/eckit/sql/expression/function/FunctionSUM.h index 58d3aca9c..f74f9e6e0 100644 --- a/src/eckit/sql/expression/function/FunctionSUM.h +++ b/src/eckit/sql/expression/function/FunctionSUM.h @@ -40,6 +40,7 @@ class FunctionSUM : public FunctionExpression { void prepare(SQLSelect&) override; void cleanup(SQLSelect&) override; void partialResult() override; + using FunctionExpression::eval; double eval(bool& missing) const override; bool isAggregate() const override { return true; } bool resultNULL_; diff --git a/src/eckit/sql/expression/function/FunctionTDIFF.h b/src/eckit/sql/expression/function/FunctionTDIFF.h index c5f13f2fa..d0acae521 100644 --- a/src/eckit/sql/expression/function/FunctionTDIFF.h +++ b/src/eckit/sql/expression/function/FunctionTDIFF.h @@ -37,6 +37,7 @@ class FunctionTDIFF : public FunctionIntegerExpression { // -- Overridden methods const eckit::sql::type::SQLType* type() const override; + using FunctionIntegerExpression::eval; double eval(bool& missing) const override; // -- Friends diff --git a/src/eckit/sql/expression/function/FunctionTHIN.h b/src/eckit/sql/expression/function/FunctionTHIN.h index a145f4d4b..4af09b59b 100644 --- a/src/eckit/sql/expression/function/FunctionTHIN.h +++ b/src/eckit/sql/expression/function/FunctionTHIN.h @@ -37,6 +37,7 @@ class FunctionTHIN : public FunctionIntegerExpression { void prepare(SQLSelect&) override; void cleanup(SQLSelect&) override; bool isConstant() const override; + using FunctionIntegerExpression::eval; double eval(bool& missing) const override; std::shared_ptr simplify(bool&) override; bool isAggregate() const override { return false; } diff --git a/src/eckit/sql/expression/function/FunctionTIMESTAMP.cc b/src/eckit/sql/expression/function/FunctionTIMESTAMP.cc index 6eade89bf..82ce96d52 100644 --- a/src/eckit/sql/expression/function/FunctionTIMESTAMP.cc +++ b/src/eckit/sql/expression/function/FunctionTIMESTAMP.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include #include "eckit/sql/expression/function/FunctionFactory.h" diff --git a/src/eckit/sql/expression/function/FunctionTIMESTAMP.h b/src/eckit/sql/expression/function/FunctionTIMESTAMP.h index 9b87c9fd1..e37978fbf 100644 --- a/src/eckit/sql/expression/function/FunctionTIMESTAMP.h +++ b/src/eckit/sql/expression/function/FunctionTIMESTAMP.h @@ -35,6 +35,7 @@ class FunctionTIMESTAMP : public FunctionIntegerExpression { FunctionTIMESTAMP& operator=(const FunctionTIMESTAMP&); // -- Overridden methods + using FunctionIntegerExpression::eval; double eval(bool& missing) const override; // -- Friends diff --git a/src/eckit/sql/expression/function/FunctionVAR.h b/src/eckit/sql/expression/function/FunctionVAR.h index c0d3b9066..d8709e25e 100644 --- a/src/eckit/sql/expression/function/FunctionVAR.h +++ b/src/eckit/sql/expression/function/FunctionVAR.h @@ -38,6 +38,7 @@ class FunctionVAR : public FunctionExpression { protected: // -- Overridden methods + using FunctionExpression::eval; double eval(bool& missing) const override; private: diff --git a/src/eckit/sql/type/SQLBit.h b/src/eckit/sql/type/SQLBit.h index 0b10051ef..85c4bb1d1 100644 --- a/src/eckit/sql/type/SQLBit.h +++ b/src/eckit/sql/type/SQLBit.h @@ -38,6 +38,7 @@ class SQLBit : public SQLType { // None size_t size() const override; + using SQLType::output; void output(SQLOutput&, double, bool) const override; int getKind() const override { return integerType; } std::string asString(const double* val) const override; diff --git a/src/eckit/sql/type/SQLBitfield.h b/src/eckit/sql/type/SQLBitfield.h index 0be6ddfe3..58e123cd8 100644 --- a/src/eckit/sql/type/SQLBitfield.h +++ b/src/eckit/sql/type/SQLBitfield.h @@ -44,6 +44,7 @@ class SQLBitfield : public SQLType { std::map shift_; size_t size() const override; + using SQLType::output; void output(SQLOutput& s, double, bool) const override; std::string asString(const double* val) const override; const SQLType* subType(const std::string&) const override; diff --git a/src/eckit/sql/type/SQLDouble.h b/src/eckit/sql/type/SQLDouble.h index d51a6da53..6e211fb01 100644 --- a/src/eckit/sql/type/SQLDouble.h +++ b/src/eckit/sql/type/SQLDouble.h @@ -28,6 +28,7 @@ class SQLDouble : public SQLType { ~SQLDouble(); // -- Overridden methods + using SQLType::output; void output(SQLOutput&, double, bool) const override; private: diff --git a/src/eckit/sql/type/SQLInt.h b/src/eckit/sql/type/SQLInt.h index 6406819f3..4c7505d2f 100644 --- a/src/eckit/sql/type/SQLInt.h +++ b/src/eckit/sql/type/SQLInt.h @@ -33,6 +33,7 @@ class SQLInt : public SQLType { SQLInt& operator=(const SQLInt&); size_t size() const override; + using SQLType::output; void output(SQLOutput& s, double, bool) const override; int getKind() const override { return integerType; } std::string asString(const double* val) const override; diff --git a/src/eckit/sql/type/SQLReal.h b/src/eckit/sql/type/SQLReal.h index 9de42d15e..42c6372fe 100644 --- a/src/eckit/sql/type/SQLReal.h +++ b/src/eckit/sql/type/SQLReal.h @@ -45,6 +45,7 @@ class SQLReal : public SQLType { // None // -- Overridden methods + using SQLType::output; void output(SQLOutput&, double, bool) const override; // -- Class members diff --git a/src/eckit/system/Library.cc b/src/eckit/system/Library.cc index f3c1c930e..92ac5e8d6 100644 --- a/src/eckit/system/Library.cc +++ b/src/eckit/system/Library.cc @@ -30,14 +30,14 @@ #include "eckit/system/SystemInfo.h" #include "eckit/thread/AutoLock.h" #include "eckit/thread/Mutex.h" +#include "eckit/thread/ThreadSingleton.h" #include "eckit/utils/Translator.h" namespace eckit::system { //---------------------------------------------------------------------------------------------------------------------- -Library::Library(const std::string& name) : - name_(name), prefix_(name), debug_(false) { +Library::Library(const std::string& name) : name_(name), prefix_(name), debug_(false) { LibraryManager::enregister(name, this); @@ -109,25 +109,22 @@ std::string Library::libraryPath() const { return libraryPath_; } -Channel& Library::debugChannel() const { - AutoLock lock(mutex_); - - if (debugChannel_) { - return *debugChannel_; - } - std::string s = prefix_ + "_DEBUG"; +Channel& Library::debugChannel() const { + static ThreadSingleton>> debugChannels; - if (debug_) { - debugChannel_.reset(new Channel(new PrefixTarget(s))); - } - else { - debugChannel_.reset(new Channel()); + auto it = debugChannels.instance().find(this); + if (it != debugChannels.instance().end()) { + return *it->second; } - return *debugChannel_; + return *debugChannels.instance() + .emplace(this, debug_ ? std::make_unique(new PrefixTarget(prefix_ + "_DEBUG")) // + : std::make_unique()) // + .first->second.get(); } + const Configuration& Library::configuration() const { AutoLock lock(mutex_); @@ -142,7 +139,8 @@ const Configuration& Library::configuration() const { Log::debug() << "Parsing Lib " << name_ << " config file " << cfgpath << std::endl; - eckit::Configuration* cfg = cfgpath.exists() ? new eckit::YAMLConfiguration(cfgpath) : new eckit::YAMLConfiguration(std::string("")); + eckit::Configuration* cfg + = cfgpath.exists() ? new eckit::YAMLConfiguration(cfgpath) : new eckit::YAMLConfiguration(std::string("")); Log::debug() << "Lib " << name_ << " configuration: " << *cfg << std::endl; diff --git a/src/eckit/system/Library.h b/src/eckit/system/Library.h index fc8d6f46c..2d5e176e0 100644 --- a/src/eckit/system/Library.h +++ b/src/eckit/system/Library.h @@ -103,8 +103,6 @@ class Library : private eckit::NonCopyable { mutable std::string libraryPath_; mutable std::string prefixDirectory_; - mutable std::unique_ptr debugChannel_; - mutable std::unique_ptr configuration_; }; diff --git a/src/eckit/system/LibraryManager.cc b/src/eckit/system/LibraryManager.cc index 66ed4a54e..ced711832 100644 --- a/src/eckit/system/LibraryManager.cc +++ b/src/eckit/system/LibraryManager.cc @@ -16,7 +16,7 @@ #include #include // for dlopen -#include // for PATH_MAX +#include // for PATH_MAX #include "eckit/system/LibraryManager.h" @@ -209,7 +209,9 @@ class LibraryRegistry { return plib; } - Log::warning() << "Failed to load library " << dynamicLibraryName << std::endl; + Log::warning() << "Failed to load library " << dynamicLibraryName + << " dlerror: " << ::dlerror() << std::endl; + return nullptr; } @@ -234,6 +236,12 @@ class LibraryRegistry { // lets load since the associated library isn't registered void* libhandle = loadDynamicLibrary(lib); + if (!libhandle) { + std::ostringstream ss; + ss << "Failed to load library " << lib; + throw FailedSystemCall(ss.str().c_str(), Here()); + } + // the plugin should self-register when the library loads Plugin* plugin = lookupPlugin(name); if (plugin) { diff --git a/src/eckit/system/SystemInfoFreeBSD.cc b/src/eckit/system/SystemInfoFreeBSD.cc index 8ac32d627..28c7ce202 100644 --- a/src/eckit/system/SystemInfoFreeBSD.cc +++ b/src/eckit/system/SystemInfoFreeBSD.cc @@ -13,7 +13,7 @@ /// @author Simon Smart /// @date March 2017 -#include +#include #include #include diff --git a/src/eckit/testing/Test.h b/src/eckit/testing/Test.h index 23bf9d4bb..da725534c 100644 --- a/src/eckit/testing/Test.h +++ b/src/eckit/testing/Test.h @@ -453,20 +453,20 @@ int run_tests(int argc, char* argv[], bool initEckitMain = true) { #define EXPECT_MSG(expr, msg_callback) \ do { \ if (!(expr)) { \ - auto msg = msg_callback; \ - msg(); \ + auto _msg = msg_callback; \ + _msg(); \ throw eckit::testing::TestException("Condition failed: " #expr, Here()); \ } \ } while (false) #define EXPECT_EQUAL(a, b) \ - EXPECT_MSG(a == b, [=]() { \ + EXPECT_MSG(a == b, [&]() { \ std::cerr << eckit::Colour::red << "FAILED " << #a " == " << #b << " evaluated as [" << a << "] == [" << b \ << "]" << eckit::Colour::reset << std::endl; \ };) #define EXPECT_NOT_EQUAL(a, b) \ - EXPECT_MSG(a != b, [=]() { \ + EXPECT_MSG(a != b, [&]() { \ std::cerr << eckit::Colour::red << "FAILED " << #a " != " << #b << " evaluated as [" << a << "] != [" << b \ << "]" << eckit::Colour::reset << std::endl; \ };) diff --git a/src/eckit/thread/ThreadControler.cc b/src/eckit/thread/ThreadControler.cc index 650ebada9..d14a80379 100644 --- a/src/eckit/thread/ThreadControler.cc +++ b/src/eckit/thread/ThreadControler.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include "eckit/log/Log.h" #include "eckit/runtime/Main.h" diff --git a/src/eckit/thread/ThreadSingleton.h b/src/eckit/thread/ThreadSingleton.h index 18ce0f0dd..3fa317e23 100644 --- a/src/eckit/thread/ThreadSingleton.h +++ b/src/eckit/thread/ThreadSingleton.h @@ -83,6 +83,7 @@ ThreadSingleton::~ThreadSingleton() { T* value = (T*)::pthread_getspecific(key_); if (value) { ::pthread_key_delete(key_); + once_ = PTHREAD_ONCE_INIT; delete value; } } diff --git a/src/eckit/types/FloatCompare.cc b/src/eckit/types/FloatCompare.cc index 4322fe784..000cdb804 100644 --- a/src/eckit/types/FloatCompare.cc +++ b/src/eckit/types/FloatCompare.cc @@ -2,7 +2,7 @@ // Some of the math.h/cmath functions are not clean when switching to C++11 #if __cplusplus <= 199711L -#include +#include #else #include #define fpclassify(x) std::fpclassify((x)) diff --git a/src/eckit/types/Fraction.cc b/src/eckit/types/Fraction.cc index 90ee323b2..992126b77 100644 --- a/src/eckit/types/Fraction.cc +++ b/src/eckit/types/Fraction.cc @@ -103,7 +103,7 @@ Fraction::Fraction(double x) { x = 1.0 / (x - a); - if (x > std::numeric_limits::max()) { + if (x > static_cast(std::numeric_limits::max())) { break; } diff --git a/src/eckit/types/Time.cc b/src/eckit/types/Time.cc index f10eb9ba8..402253f22 100644 --- a/src/eckit/types/Time.cc +++ b/src/eckit/types/Time.cc @@ -18,6 +18,13 @@ #include "eckit/utils/Hash.h" #include "eckit/utils/Tokenizer.h" +namespace { + static thread_local std::regex digits_time_("^-?[0-9]+$"); + static thread_local std::regex float_hours_("^-?[0-9]*\\.[0-9]+$"); + static thread_local std::regex hhmmss_("^([0-9]+):([0-5]?[0-9])(:[0-5]?[0-9])?$"); + static thread_local std::regex ddhhmmss_("^-?([0-9]+[dD])?([0-9]+[hH])?([0-9]+[mM])?([0-9]+[sS])?$"); +} + namespace eckit { //---------------------------------------------------------------------------------------------------------------------- @@ -45,7 +52,7 @@ Time::Time(const std::string& s, bool extended) { long dd = 0; std::smatch m; - if (std::regex_match (s, m, std::regex("^-?[0-9]+$"))) { // only digits + if (std::regex_match (s, m, digits_time_)) { long t = std::stol(s); int sign = (s[0] == '-' ? 1 : 0); if (extended || s.length() <= 2+sign) { // cases: h, hh, (or hhh..h for step parsing) @@ -62,7 +69,7 @@ Time::Time(const std::string& s, bool extended) { } } else { - if (std::regex_match (s, m, std::regex("^-?[0-9]*\\.[0-9]+$"))) { // floating point (hours) + if (std::regex_match (s, m, float_hours_)) { long sec = std::round(std::stod(s)*3600); hh = sec/3600; sec -= hh*3600; @@ -71,7 +78,7 @@ Time::Time(const std::string& s, bool extended) { ss = sec; } else { - if (std::regex_match (s, m, std::regex("^([0-9]+):([0-5]?[0-9])(:[0-5]?[0-9])?$"))) { + if (std::regex_match (s, m, hhmmss_)) { for (int i=1; i -#include +#include +#include #include #include #include diff --git a/src/eckit/utils/MD4.h b/src/eckit/utils/MD4.h index 9bb57e333..036c14937 100644 --- a/src/eckit/utils/MD4.h +++ b/src/eckit/utils/MD4.h @@ -16,7 +16,7 @@ #if eckit_HAVE_SSL #include #else -#error "eckit was not configured with OpenSSL, SHA1 is disabled. Use conditional eckit_HAVE_SSL from eckit/eckit.h" +#error "eckit was not configured with OpenSSL, MD4 is disabled. Use conditional eckit_HAVE_SSL from eckit/eckit.h" #endif #ifndef MD4_DIGEST_LENGTH diff --git a/src/eckit/utils/RLE.cc b/src/eckit/utils/RLE.cc index 11b828699..01d40b709 100644 --- a/src/eckit/utils/RLE.cc +++ b/src/eckit/utils/RLE.cc @@ -8,6 +8,8 @@ * does it submit to any jurisdiction. */ +#include +#include #include #include "eckit/log/Log.h" @@ -21,10 +23,16 @@ namespace eckit { // Version 2: warning all numbers must be signed but positive... template -class dummy_iterator : public std::iterator { +class dummy_iterator { public: + using iterator_category = std::output_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + dummy_iterator() {} - dummy_iterator& operator=(const T& value) { return *this; } + dummy_iterator& operator=(const value_type&) { return *this; } dummy_iterator& operator*() { return *this; } dummy_iterator& operator++() { return *this; } dummy_iterator& operator++(int) { return *this; } diff --git a/src/eckit/utils/Regex.cc b/src/eckit/utils/Regex.cc index 219aaa738..f2ee2042b 100644 --- a/src/eckit/utils/Regex.cc +++ b/src/eckit/utils/Regex.cc @@ -114,6 +114,39 @@ Regex& Regex::operator=(const Regex& other) { return *this; } +std::string Regex::escape(std::string_view str) { + std::string ret; + // Reserve twice the size of str for worst-case + ret.reserve(str.size()*2); + + for (const char& c: str) { + switch(c) { + case '.': + case '^': + case '$': + case '*': + case '+': + case '-': + case '?': + case '(': + case ')': + case '[': + case ']': + case '{': + case '}': + case '\\': + case '|': + ret.insert(ret.end(), '\\'); + [[fallthrough]]; + default: + ret.insert(ret.end(), c); + } + } + + return ret; +} + + //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit diff --git a/src/eckit/utils/Regex.h b/src/eckit/utils/Regex.h index 33f53c601..8d817ce63 100644 --- a/src/eckit/utils/Regex.h +++ b/src/eckit/utils/Regex.h @@ -17,6 +17,7 @@ #include #include +#include namespace eckit { @@ -41,6 +42,8 @@ class Regex { operator const std::string&() const { return str_; } bool operator==(const Regex& other) const { return str_ == other.str_; } + + static std::string escape(std::string_view); protected: // methods void print(std::ostream&) const; diff --git a/src/eckit/value/Params.h b/src/eckit/value/Params.h index 540b952e1..e7448ccb9 100644 --- a/src/eckit/value/Params.h +++ b/src/eckit/value/Params.h @@ -124,8 +124,8 @@ class Params { typedef std::map factory_map; static factory_map& factories(); - Params(Concept* concept) : - self_(concept) {} + Params(Concept* _concept) : + self_(_concept) {} struct Concept { virtual ~Concept() {} diff --git a/src/eckit/web/HttpResource.cc b/src/eckit/web/HttpResource.cc index ca800385b..811b39119 100644 --- a/src/eckit/web/HttpResource.cc +++ b/src/eckit/web/HttpResource.cc @@ -229,14 +229,11 @@ void HttpResource::dispatch(eckit::Stream&, std::istream&, HttpStream& out, Url& std::ostringstream oss; oss << "Unsupported HTTP method " << method << " url=" << url; - throw eckit::MethodNotYetImplemented(oss.str()); + throw eckit::NotImplemented(oss.str()); } catch (eckit::HttpError& e) { error(url, out, e, e.status()); } - catch (eckit::MethodNotYetImplemented& e) { - error(url, out, e, HttpError::NOT_IMPLEMENTED); - } catch (eckit::NotImplemented& e) { error(url, out, e, HttpError::NOT_IMPLEMENTED); } diff --git a/src/eckit/web/HttpStream.cc b/src/eckit/web/HttpStream.cc index a2951b02e..155e564c2 100644 --- a/src/eckit/web/HttpStream.cc +++ b/src/eckit/web/HttpStream.cc @@ -8,7 +8,9 @@ * does it submit to any jurisdiction. */ +#include #include +#include #include "eckit/exception/Exceptions.h" #include "eckit/io/DataHandle.h" @@ -50,18 +52,23 @@ static int xindex = std::ios::xalloc(); typedef std::vector VC; -class back_encoder_iterator : public std::iterator { +class back_encoder_iterator { VC& container; void push(const char* p) { - while (*p) { + while (*p != static_cast(0)) { container.push_back(*p++); } } public: - back_encoder_iterator(VC& v) : - container(v) {} - back_encoder_iterator& operator=(char); + using iterator_category = std::output_iterator_tag; + using value_type = VC::value_type; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + explicit back_encoder_iterator(VC& v) : container(v) {} + back_encoder_iterator& operator=(value_type); back_encoder_iterator& operator*() { return *this; } back_encoder_iterator& operator++() { return *this; } back_encoder_iterator& operator++(int) { return *this; } @@ -153,15 +160,15 @@ void HttpBuf::write(std::ostream& out, Url& url) { // out << ""; #if 0 - Log::debug() << "Data: " << std::endl; + Log::debug() << "Data: " << std::endl; - for (std::vector::iterator i = buffer_.begin(); i != buffer_.end(); ++i) - if (isprint(*i) || isspace(*i)) - Log::debug() << *i; - else - break; + for (std::vector::iterator i = buffer_.begin(); i != buffer_.end(); ++i) + if (isprint(*i) || isspace(*i)) + Log::debug() << *i; + else + break; - Log::debug() << std::endl; + Log::debug() << std::endl; #endif } diff --git a/src/sandbox/multi_socket_client.cc b/src/sandbox/multi_socket_client.cc index 231a64d2b..bf16d2220 100644 --- a/src/sandbox/multi_socket_client.cc +++ b/src/sandbox/multi_socket_client.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include #include #include diff --git a/src/sandbox/udp_server.cc b/src/sandbox/udp_server.cc index 7bbddc982..6b69339a6 100644 --- a/src/sandbox/udp_server.cc +++ b/src/sandbox/udp_server.cc @@ -1,11 +1,11 @@ #include -#include +#include #include #include -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 5d9f45fff..eaff5c5a0 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -1,54 +1,76 @@ ecbuild_add_executable( TARGET eckit_version OUTPUT_NAME eckit-version - CONDITION HAVE_BUILD_TOOLS SOURCES eckit-version.cc LIBS eckit_option eckit ) +if( NOT eckit_HAVE_BUILD_TOOLS ) + return() +endif() + ecbuild_add_executable( TARGET eckit_info OUTPUT_NAME eckit-info - CONDITION HAVE_BUILD_TOOLS SOURCES eckit-info.cc LIBS eckit_option eckit ) ecbuild_add_executable( TARGET eckit_codec_list OUTPUT_NAME eckit-codec-list - CONDITION HAVE_BUILD_TOOLS AND HAVE_ECKIT_CODEC + CONDITION eckit_HAVE_ECKIT_CODEC SOURCES eckit-codec-list.cc LIBS eckit_codec eckit_option ) +ecbuild_add_executable( TARGET eckit_grid + OUTPUT_NAME eckit-grid + CONDITION eckit_HAVE_ECKIT_GEO + SOURCES eckit-grid.cc + LIBS eckit_geo eckit_option ) + +ecbuild_add_executable( TARGET eckit_grid_list + OUTPUT_NAME eckit-grid-list + CONDITION eckit_HAVE_ECKIT_GEO + SOURCES eckit-grid-list.cc + LIBS eckit_geo eckit_option ) + +ecbuild_add_executable( TARGET eckit_grid_nearest + OUTPUT_NAME eckit-grid-nearest + CONDITION eckit_HAVE_ECKIT_GEO + SOURCES eckit-grid-nearest.cc + LIBS eckit_geo eckit_option ) + +ecbuild_add_executable( TARGET eckit_grid_spec + OUTPUT_NAME eckit-grid-spec + CONDITION eckit_HAVE_ECKIT_GEO + SOURCES eckit-grid-spec.cc + LIBS eckit_geo eckit_option ) + ### NOT TO INSTALL ecbuild_add_executable( TARGET dhcopy OUTPUT_NAME eckit-dhcopy - CONDITION HAVE_BUILD_TOOLS NOINSTALL SOURCES dhcopy.cc LIBS eckit_option eckit ) ecbuild_add_executable( TARGET syslog_server OUTPUT_NAME eckit-syslog-server - CONDITION HAVE_BUILD_TOOLS NOINSTALL SOURCES syslog-server.cc LIBS eckit ) ecbuild_add_executable( TARGET syslog_client OUTPUT_NAME eckit-syslog-client - CONDITION HAVE_BUILD_TOOLS NOINSTALL SOURCES syslog-client.cc LIBS eckit ) ecbuild_add_executable( TARGET eckit_rsync OUTPUT_NAME eckit-rsync - CONDITION HAVE_BUILD_TOOLS AND eckit_HAVE_RSYNC + CONDITION eckit_HAVE_RSYNC NOINSTALL SOURCES rsync.cc LIBS eckit_option eckit ) ecbuild_add_executable( TARGET eckit_hash OUTPUT_NAME eckit-hash - CONDITION HAVE_BUILD_TOOLS NOINSTALL SOURCES eckit-hash.cc LIBS eckit_option eckit ) diff --git a/src/tools/eckit-grid-list.cc b/src/tools/eckit-grid-list.cc new file mode 100644 index 000000000..6c03ff999 --- /dev/null +++ b/src/tools/eckit-grid-list.cc @@ -0,0 +1,42 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Grid.h" +#include "eckit/log/Log.h" +#include "eckit/option/EckitTool.h" + + +namespace eckit::tools { + + +struct EckitGridList final : EckitTool { + EckitGridList(int argc, char** argv) : EckitTool(argc, argv) {} + + void execute(const option::CmdArgs&) override { + geo::GridFactory::list(Log::info()); + Log::info() << std::endl; + } + + void usage(const std::string& tool) const override { + Log::info() << "\n" + "Usage: " + << tool << "[options] ..." << std::endl; + } +}; + + +} // namespace eckit::tools + + +int main(int argc, char** argv) { + eckit::tools::EckitGridList app(argc, argv); + return app.start(); +} diff --git a/src/tools/eckit-grid-nearest.cc b/src/tools/eckit-grid-nearest.cc new file mode 100644 index 000000000..27af0e012 --- /dev/null +++ b/src/tools/eckit-grid-nearest.cc @@ -0,0 +1,101 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/Grid.h" +#include "eckit/geo/Point.h" +#include "eckit/geo/Search.h" +#include "eckit/geo/grid/Unstructured.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/log/Log.h" +#include "eckit/option/CmdArgs.h" +#include "eckit/option/EckitTool.h" +#include "eckit/option/SimpleOption.h" +#include "eckit/option/VectorOption.h" + +//---------------------------------------------------------------------------------------------------------------------- + +namespace eckit { + +class EckitGridNearest final : public EckitTool { +public: + EckitGridNearest(int argc, char** argv) : EckitTool(argc, argv) { + options_.push_back(new option::SimpleOption("uid", "by grid unique identifier, instead of name")); + options_.push_back(new option::VectorOption("nearest-point", "nearest point location (lon/lat)", 2)); + options_.push_back(new option::SimpleOption("nearest-k", "nearest k points")); + } + +private: + void execute(const option::CmdArgs& args) override { + auto uid = args.getBool("uid", false); + + geo::PointLonLat nearest_point{0, 0}; + size_t nearest_k = 0; + if (std::vector point; args.get("nearest-point", point)) { + ASSERT(point.size() == 2); + nearest_point = {point[0], point[1]}; + nearest_k = args.getUnsigned("nearest-k", 1); + } + + auto& out = Log::info(); + out.precision(args.getInt("precision", 16)); + + for (const auto& arg : args) { + std::unique_ptr spec(new geo::spec::Custom({{uid ? "uid" : "name", std::string(arg)}})); + std::unique_ptr grid(geo::GridFactory::build(*spec)); + + out << "size: " << grid->size() << std::endl; + + for (const auto& p : *grid) { + out << p << std::endl; + } + + // for (const auto& p : geo::grid::UnstructuredGrid(*grid)) { + // out << p << std::endl; + // } + + if (nearest_k > 0) { + geo::SearchLonLat search; + search.build(grid->to_points()); + + const auto* sep = ""; + for (auto& near : search.kNearestNeighbours(nearest_point, nearest_k)) { + out << sep << near; + sep = ", "; + } + out << std::endl; + } + + // it += grid->size() - 1; + // out << "last: " << *it << std::endl; + // ASSERT(it == grid->rbegin()); + } + } + + void usage(const std::string& tool) const override { + Log::info() << "\n" + "Usage: " + << tool << "[options] ..." << std::endl; + } + + int minimumPositionalArguments() const override { return 0; } +}; + +} // namespace eckit + +//---------------------------------------------------------------------------------------------------------------------- + +int main(int argc, char** argv) { + eckit::EckitGridNearest app(argc, argv); + return app.start(); +} diff --git a/src/tools/eckit-grid-spec.cc b/src/tools/eckit-grid-spec.cc new file mode 100644 index 000000000..a2953ffae --- /dev/null +++ b/src/tools/eckit-grid-spec.cc @@ -0,0 +1,65 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include +#include + +#include "eckit/geo/Grid.h" +#include "eckit/log/Log.h" +#include "eckit/option/CmdArgs.h" +#include "eckit/option/EckitTool.h" +#include "eckit/parser/YAMLParser.h" + + +namespace eckit { + +class EckitGridSpec final : public EckitTool { +public: + EckitGridSpec(int argc, char** argv) : EckitTool(argc, argv) {} + +private: + void execute(const option::CmdArgs& args) override { + std::string user; + + if (args.count() == 0) { + std::ostringstream out; + YAMLParser(std::cin).parse().dump(out); + user = out.str(); + } + else { + for (const auto& arg : args) { + user += " " + arg; + } + } + + std::unique_ptr grid(geo::GridFactory::make_from_string(user)); + auto spec = grid->spec_str(); + Log::info() << spec << std::endl; + } + + void usage(const std::string& tool) const override { + Log::info() << "\n" + "Usage: \n" + << tool << " \n" + << "echo | " << tool << std::endl; + } + + int minimumPositionalArguments() const override { return 0; } +}; + +} // namespace eckit + + +int main(int argc, char** argv) { + eckit::EckitGridSpec app(argc, argv); + return app.start(); +} diff --git a/src/tools/eckit-grid.cc b/src/tools/eckit-grid.cc new file mode 100644 index 000000000..4fe4594bf --- /dev/null +++ b/src/tools/eckit-grid.cc @@ -0,0 +1,115 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include +#include +#include + +#include "eckit/geo/Grid.h" +#include "eckit/geo/Point.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/log/JSON.h" +#include "eckit/log/Log.h" +#include "eckit/option/CmdArgs.h" +#include "eckit/option/EckitTool.h" +#include "eckit/option/SimpleOption.h" +#include "eckit/parser/YAMLParser.h" + + +namespace eckit { + +class EckitGrid final : public EckitTool { +public: + EckitGrid(int argc, char** argv) : EckitTool(argc, argv) { + options_.push_back(new option::SimpleOption("grid", "grid spec")); + options_.push_back(new option::SimpleOption("bounding-box", "Bounding box")); + options_.push_back(new option::SimpleOption("precision", "Output precision")); + } + +private: + void usage(const std::string& tool) const override { + Log::info() << "\n" + "Usage: " + << tool << "[options] ..." << std::endl; + } + + int minimumPositionalArguments() const override { return 1; } + + void execute(const option::CmdArgs& args) override { + std::stringstream stream; + eckit::JSON out(stream); + out.precision(args.getInt("precision", 16)); + + std::string user; + for (const auto& arg : args) { + user += " " + arg; + } + + std::unique_ptr grid([](const std::string& str) -> const geo::Grid* { + std::unique_ptr spec(geo::spec::Custom::make_from_value(YAMLParser::decodeString(str))); + return geo::GridFactory::build(*spec); + }(user)); + + out << "spec" << grid->spec_str(); + out << "uid" << grid->uid(); + out << "size" << grid->size(); + + + // Earth's equator ~= 40075 km + auto deg_km + = [](double deg) { return std::to_string(deg) + " deg, " + std::to_string(deg * 40075. / 360.) + " km"; }; + + auto nx = 1; // grid->nx(); + auto ny = 1; // grid->ny(); + + out << "shape"; + (out.startList() << nx << ny).endList(); + + // out << "resolution N-S" << deg_km((grid->y().front() - grid->y().back()) / (ny - 1)); + // out << "resolution E-W equator" << deg_km(360. / static_cast(grid->nx(ny / 2))); + // out << "resolution E-W midlat" << deg_km(360. * std::cos(grid->y(ny / 4) * M_PI / 180.) / + // static_cast(grid->nx(ny / 4))) << "resolution E-W pole" << deg_km(360. * + // std::cos(grid->y().front() * M_PI / 180.) / static_cast(grid->nx().front())); + + out << "spectral truncation linear" << (ny - 1); + out << "spectral truncation quadratic" << (static_cast(std::floor(2. / 3. * ny + 0.5)) - 1); + out << "spectral truncation cubic" << (static_cast(std::floor(0.5 * ny + 0.5)) - 1); + + { + auto points = grid->to_points(); + auto first = std::get(points.front()); + auto last = std::get(points.back()); + + out << "first"; + (out.startList() << first.lon << first.lat).endList(); + + out << "last"; + (out.startList() << last.lon << last.lat).endList(); + } + + { + auto bbox = grid->boundingBox(); + out << "bounding box"; + (out.startList() << bbox.north << bbox.west << bbox.south << bbox.east).endList(); + } + + Log::info() << stream.str() << std::endl; + } +}; + +} // namespace eckit + + +int main(int argc, char** argv) { + eckit::EckitGrid app(argc, argv); + return app.start(); +} diff --git a/src/tools/eckit-info.cc b/src/tools/eckit-info.cc index 6b5e8bea8..757edbfb5 100644 --- a/src/tools/eckit-info.cc +++ b/src/tools/eckit-info.cc @@ -16,6 +16,7 @@ #include "eckit/log/Log.h" #include "eckit/option/CmdArgs.h" #include "eckit/option/EckitTool.h" +#include "eckit/option/SimpleOption.h" #include "eckit/system/Library.h" #include "eckit/system/LibraryManager.h" diff --git a/src/tools/syslog-client.cc b/src/tools/syslog-client.cc index b79455a61..b84a31445 100644 --- a/src/tools/syslog-client.cc +++ b/src/tools/syslog-client.cc @@ -1,10 +1,10 @@ #include -#include +#include #include #include -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/src/tools/syslog-server.cc b/src/tools/syslog-server.cc index b5bec938e..f0f17ff3d 100644 --- a/src/tools/syslog-server.cc +++ b/src/tools/syslog-server.cc @@ -3,12 +3,12 @@ */ #include -#include +#include #include #include -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ae5779df9..30d3bc5a6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,21 +14,25 @@ add_subdirectory( option ) add_subdirectory( parser ) add_subdirectory( runtime ) add_subdirectory( serialisation ) +add_subdirectory( system ) add_subdirectory( testing ) add_subdirectory( thread ) add_subdirectory( types ) add_subdirectory( utils ) add_subdirectory( value ) -add_subdirectory( system ) -if( HAVE_ECKIT_SQL ) +if( eckit_HAVE_ECKIT_SQL ) add_subdirectory( sql ) endif() -if( HAVE_ECKIT_CODEC ) +if( eckit_HAVE_ECKIT_CODEC ) add_subdirectory( codec ) endif() -if( HAVE_EXPERIMENTAL ) +if( eckit_HAVE_ECKIT_GEO ) + add_subdirectory( geo ) +endif() + +if( eckit_HAVE_EXPERIMENTAL ) add_subdirectory( experimental ) endif() diff --git a/tests/config/CMakeLists.txt b/tests/config/CMakeLists.txt index 2a810e523..2b69b342d 100644 --- a/tests/config/CMakeLists.txt +++ b/tests/config/CMakeLists.txt @@ -1,8 +1,13 @@ -ecbuild_add_test( TARGET eckit_test_config_resource - SOURCES test_resource.cc - ARGS -integer 100 -listlong 88,99,11,22 - LIBS eckit ) +ecbuild_add_test( + TARGET eckit_test_config_resource + SOURCES test_resource.cc + ARGS -integer 100 -listlong 88,99,11,22 + LIBS eckit +) + +ecbuild_add_test( + TARGET eckit_test_config_configuration + SOURCES test_configuration.cc + LIBS eckit +) -ecbuild_add_test( TARGET eckit_test_config_configuration - SOURCES test_configuration.cc - LIBS eckit ) diff --git a/tests/config/test_configuration.cc b/tests/config/test_configuration.cc index 623ed2646..77a25c10f 100644 --- a/tests/config/test_configuration.cc +++ b/tests/config/test_configuration.cc @@ -13,65 +13,42 @@ #include "eckit/config/LocalConfiguration.h" #include "eckit/config/YAMLConfiguration.h" #include "eckit/filesystem/PathName.h" -#include "eckit/log/Log.h" #include "eckit/testing/Test.h" -#include "eckit/types/Types.h" +#include "eckit/types/FloatCompare.h" #include "eckit/utils/Hash.h" -using namespace std; -using namespace eckit; -using namespace eckit::testing; - namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- -template -std::vector make_vector(const T& t1, const T& t2) { - std::vector result; - result.push_back(t1); - result.push_back(t2); - return result; -} -template -std::vector make_vector(const T& t1, const T& t2, const T& t3) { - std::vector result; - result.push_back(t1); - result.push_back(t2); - result.push_back(t3); - return result; -} - -//---------------------------------------------------------------------------------------------------------------------- - CASE("test_configuration_interface") { - bool value_bool = bool(true); - int value_int = int(1); - long value_long = long(2); - long long value_long_long = 2ll; - size_t value_size_t = size_t(3); - float value_float = float(1.234567); - double value_double = double(1.2345678912345789123456789); - std::string value_string = std::string("string"); - std::vector value_arr_int = make_vector(1, 2, 3); - std::vector value_arr_long = make_vector(4l, 5l); - std::vector value_arr_long_long = make_vector(4ll, 5ll); - std::vector value_arr_size_t = make_vector(std::size_t{6}, std::size_t{7}); - std::vector value_arr_float = make_vector(1.234567f, 2.345678f); - std::vector value_arr_double = make_vector(1.234567, 2.345678); - std::vector value_arr_string = make_vector(std::string("hello"), std::string("world")); + bool value_bool = true; + int value_int = 1; + long value_long = 2; + long long value_long_long = 2; + size_t value_size_t = 3; + float value_float = 1.234567; + double value_double = 1.2345678912345789123456789; + std::string value_string = "string"; + std::vector value_arr_int = {1, 2, 3}; + std::vector value_arr_long = {4, 5}; + std::vector value_arr_long_long = {4, 5}; + std::vector value_arr_size_t = {6, 7}; + std::vector value_arr_float = {1.234567, 2.345678}; + std::vector value_arr_double = {1.234567, 2.345678}; + std::vector value_arr_string = {"hello", "world"}; std::int32_t value_int32 = value_int; std::int64_t value_int64 = value_long_long; - std::vector value_arr_int32{4, 5}; - std::vector value_arr_int64{4ll, 5ll}; - - bool result_bool; - int result_int; - long result_long; - long long result_long_long; - size_t result_size_t; - float result_float; - double result_double; + std::vector value_arr_int32 = {4, 5}; + std::vector value_arr_int64 = {4, 5}; + + bool result_bool = false; + int result_int = 0; + long result_long = 0; + long long result_long_long = 0; + size_t result_size_t = 0; + float result_float = 0; + double result_double = 0; std::string result_string; std::vector result_arr_int; std::vector result_arr_long; @@ -80,8 +57,8 @@ CASE("test_configuration_interface") { std::vector result_arr_float; std::vector result_arr_double; std::vector result_arr_string; - std::int32_t result_int32; - std::int64_t result_int64; + std::int32_t result_int32 = 0; + std::int64_t result_int64 = 0; std::vector result_arr_int32; std::vector result_arr_int64; @@ -347,12 +324,37 @@ CASE("YAML configuration converts numbers to strings or numbers") { //---------------------------------------------------------------------------------------------------------------------- + +CASE("YAML configuration with null value") { + const char* text = R"YAML( +--- +base: + nothing : null +)YAML"; + + std::string cfgtxt(text); + + YAMLConfiguration conf(cfgtxt); + + LocalConfiguration local; + + EXPECT_NO_THROW(conf.get("base", local)); + + std::cerr << Colour::green << local << Colour::reset << std::endl; + + EXPECT(local.isNull("nothing")); +} + +//---------------------------------------------------------------------------------------------------------------------- + CASE("test_local_configuration") { LocalConfiguration local; { LocalConfiguration manager; manager.set("name", "Sidonia"); manager.set("office", 1); + manager.set("height", 1.78); + manager.set("free", false); std::vector staff(2); staff[0].set("name", "Suske"); @@ -362,6 +364,8 @@ CASE("test_local_configuration") { local.set("manager", manager); local.set("staff", staff); + + local.set("books.count", 10); } const Configuration& conf = local; @@ -369,6 +373,9 @@ CASE("test_local_configuration") { std::vector staff; EXPECT(conf.get("manager", manager)); + + EXPECT(conf.isSubConfigurationList("staff")); + EXPECT(conf.isConvertible("staff",staff)); EXPECT(conf.get("staff", staff)); std::string name; @@ -388,6 +395,47 @@ CASE("test_local_configuration") { EXPECT(staff[1].get("office", office)); EXPECT(name == std::string("Wiske")); EXPECT(office == 3); + + int books_count; + EXPECT(conf.has("books")); + EXPECT(conf.get("books.count", books_count)); + EXPECT(books_count == 10); + + LocalConfiguration books; + EXPECT(conf.isSubConfiguration("books")); + conf.get("books",books); + EXPECT(books.getInt("count") == 10); + + EXPECT(conf.isConvertible("books")); + + EXPECT(conf.isList("staff")); + EXPECT(conf.isIntegral("manager.office")); + EXPECT(!conf.isFloatingPoint("manager.office")); + EXPECT(conf.isConvertible("manager.office")); + EXPECT(conf.isConvertible("manager.office")); + EXPECT(conf.isConvertible("manager.office")); + EXPECT(conf.isConvertible("manager.office")); + EXPECT(conf.isConvertible("manager.office")); + EXPECT(conf.isConvertible("manager.office")); + EXPECT(!conf.isConvertible("manager.office")); + EXPECT(!conf.isConvertible("manager.office")); + EXPECT(!conf.isConvertible("manager.office")); + + EXPECT(conf.isConvertible("manager.height")); + EXPECT(conf.isConvertible("manager.height")); + EXPECT(!conf.isConvertible("manager.height")); + EXPECT(!conf.isConvertible("manager.height")); + EXPECT(!conf.isConvertible("manager.height")); + EXPECT(!conf.isConvertible("manager.height")); + + double manager_height; + EXPECT(conf.get("manager.height", manager_height)); + EXPECT(manager_height == 1.78); + + local.set("a", "a"); + const eckit::Parametrisation& p = conf; + EXPECT(!p.has("a.b")); + } CASE("Hash a configuration") { @@ -409,5 +457,5 @@ CASE("Hash a configuration") { } // namespace eckit::test int main(int argc, char** argv) { - return run_tests(argc, argv); + return eckit::testing::run_tests(argc, argv); } diff --git a/tests/container/test_sharedmemarray.cc b/tests/container/test_sharedmemarray.cc index 045ca92ec..c2f3e8321 100644 --- a/tests/container/test_sharedmemarray.cc +++ b/tests/container/test_sharedmemarray.cc @@ -9,7 +9,7 @@ */ #include -#include +#include #include #include #include diff --git a/tests/exception/test_exceptions.cc b/tests/exception/test_exceptions.cc index d2c030a83..c11cd85d9 100644 --- a/tests/exception/test_exceptions.cc +++ b/tests/exception/test_exceptions.cc @@ -20,6 +20,10 @@ std::string evaluate_message(bool answer_is_correct) { if (answer_is_correct) { // this should never execute, and therefore the code below never reached throw NotImplemented(Here()); +// Suppress warning "statement is unreachable" +#if defined(__NVCOMPILER) +#pragma diag_suppress 111 +#endif return "NON REACHABLE CODE"; } diff --git a/tests/geo/CMakeLists.txt b/tests/geo/CMakeLists.txt new file mode 100644 index 000000000..f183b58fa --- /dev/null +++ b/tests/geo/CMakeLists.txt @@ -0,0 +1,83 @@ +set(CACHE_PATH "${CMAKE_CURRENT_BINARY_DIR}/eckit_geo_cache") + +foreach(_test + area_boundingbox + area_polygon + cache + figure + geometry_sphere + great_circle + grid + grid_healpix + grid_reduced_gg + grid_regular_gg + grid_regular_ll + grid_reorder + grid_to_points + increments + iterator + kdtree + ordering + point + point2 + point3 + pointlonlat + pointlonlatr + projection + projection_ll_to_xyz + projection_mercator + projection_plate-caree + projection_proj + projection_rotation + range + search + spec + spec_custom + spec_layered + util ) + ecbuild_add_test( + TARGET eckit_test_geo_${_test} + SOURCES ${_test}.cc + LIBS eckit_geo ) +endforeach() + +if(eckit_HAVE_GEO_GRID_ORCA) + set(URL "https://get.ecmwf.int/repository/atlas/grids/orca/v0/ORCA2_T.atlas") + set(FILE "${CACHE_PATH}/eckit/geo/grid/orca/d5bde4f52ff3a9bea5629cd9ac514410.atlas") + set(MD5 "f279b48c171409f46bfd27dff98d454a") + + file(DOWNLOAD ${URL} ${FILE} EXPECTED_MD5 ${MD5} TIMEOUT 30 STATUS DOWNLOAD_STATUS) + + list(GET DOWNLOAD_STATUS 0 STATUS_CODE) + if(${STATUS_CODE} EQUAL 0) + message(STATUS "File downloaded: '${FILE}'") + else() + list(GET DOWNLOAD_STATUS 1 STATUS_MESSAGE) + message(FATAL_ERROR "File download failed: ${STATUS_MESSAGE}, file: '${FILE}'") + endif() + + ecbuild_add_test( + TARGET eckit_test_geo_grid_orca + SOURCES grid_orca.cc + LIBS eckit_geo + ENVIRONMENT "ECKIT_GEO_CACHE_PATH=${CACHE_PATH}" ) +endif() + +ecbuild_add_test( + TARGET eckit_test_geo_tool_grid_spec_1_1 + COMMAND eckit_grid_spec ARGS "grid: 1/1.0" ) + +set_tests_properties( + eckit_test_geo_tool_grid_spec_1_1 + PROPERTIES + PASS_REGULAR_EXPRESSION [=[{"grid":\[1,1\]}]=] ) + +ecbuild_add_test( + TARGET eckit_test_geo_tool_grid_spec_025_01 + COMMAND eckit_grid_spec ARGS "grid: .250/001e-1" ) + +set_tests_properties( + eckit_test_geo_tool_grid_spec_025_01 + PROPERTIES + PASS_REGULAR_EXPRESSION [=[{"grid":\[0\.25,0\.1\]}]=] ) + diff --git a/tests/geo/area_boundingbox.cc b/tests/geo/area_boundingbox.cc new file mode 100644 index 000000000..53af3e3fa --- /dev/null +++ b/tests/geo/area_boundingbox.cc @@ -0,0 +1,139 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/area/BoundingBox.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("global") { + area::BoundingBox a; + area::BoundingBox b(90, 0, -90, 360); + EXPECT_EQUAL(a, b); +} + + +CASE("latitude (checks)") { + EXPECT_THROWS(area::BoundingBox(-90, 0, 90, 360)); // fails South<=North + EXPECT_NO_THROW(area::BoundingBox(90, 0, 90, 360)); + EXPECT_NO_THROW(area::BoundingBox(-90, 0, -90, 360)); +} + + +CASE("longitude (normalisation)") { + for (double west : {-900, -720, -540, -360, -180, 0, 180, 360, 540, 720, 900}) { + area::BoundingBox a(90, west, 90, west); + + EXPECT_EQUAL(a.west, west); + EXPECT(a.empty()); + + area::BoundingBox b{90, west, -90, west - 1}; + std::unique_ptr c( + area::BoundingBox::make_from_area(90, west + 42 * 360., -90, west - 42 * 360. - 1)); + + EXPECT(c->east == c->west + 360 - 1); + EXPECT(b == *c); + } +} + + +CASE("assignment") { + area::BoundingBox a(10, 1, -10, 100); + area::BoundingBox b(20, 2, -20, 200); + + EXPECT_NOT_EQUAL(a.north, b.north); + EXPECT_NOT_EQUAL(a, b); + + b = a; + + EXPECT_EQUAL(a.north, b.north); + EXPECT_EQUAL(a, b); + + b = {30., b.west, b.south, b.east}; + + EXPECT_EQUAL(b.north, 30); + EXPECT_EQUAL(a.north, 10); + + area::BoundingBox c(a); + + EXPECT_EQUAL(a.north, c.north); + EXPECT_EQUAL(a, c); + + c = {40., c.west, c.south, c.east}; + + EXPECT_EQUAL(c.north, 40); + EXPECT_EQUAL(a.north, 10); + + auto d(std::move(a)); + + EXPECT_EQUAL(d.north, 10); + + d = {50., d.west, d.south, d.east}; + + EXPECT_EQUAL(d.north, 50); +} + + +CASE("comparison") { + area::BoundingBox a(10, 1, -10, 100); + area::BoundingBox b(20, 2, -20, 200); + + EXPECT(!area::bounding_box_equal(a, b)); + + for (const auto& c : {a, b}) { + const area::BoundingBox d{c.north, c.west + 42 * PointLonLat::FULL_ANGLE, c.south, + c.east + 41 * PointLonLat::FULL_ANGLE}; + EXPECT(area::bounding_box_equal(c, d)); + } +} + + +CASE("properties") { + area::BoundingBox a{10, 1, -10, 100}; + area::BoundingBox b{20, 2, -20, 200}; + std::unique_ptr c(area::BoundingBox::make_global_prime()); + std::unique_ptr d(area::BoundingBox::make_global_antiprime()); + area::BoundingBox e; + + for (const auto& bb : {a, b, *c, *d, e}) { + EXPECT(!bb.empty()); + EXPECT(bb.contains({10, 0})); + EXPECT(bb.global() == bb.contains({0, 0})); + EXPECT(bb.global() == (bb.periodic() && bb.contains(NORTH_POLE) && bb.contains(SOUTH_POLE))); + } +} + + +CASE("intersects") { + area::BoundingBox a(10, 1, -10, 100); + area::BoundingBox b(20, 2, -20, 200); + + EXPECT(!area::bounding_box_equal(a, b)); + + for (const auto& c : {a, b}) { + const area::BoundingBox d{c.north, c.west + 42 * PointLonLat::FULL_ANGLE, c.south, + c.east + 41 * PointLonLat::FULL_ANGLE}; + EXPECT(area::bounding_box_equal(c, d)); + } +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/area_polygon.cc b/tests/geo/area_polygon.cc new file mode 100644 index 000000000..e944f37e9 --- /dev/null +++ b/tests/geo/area_polygon.cc @@ -0,0 +1,411 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/Point2.h" +#include "eckit/geo/PointLonLat.h" +#include "eckit/geo/polygon/LonLatPolygon.h" +#include "eckit/geo/polygon/Polygon.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("Polygon") { + using geo::polygon::Polygon; + + + SECTION("empty polygon") { + Polygon poly1; + Polygon poly2; + + EXPECT(poly1.sameAs(poly2)); + } + + + SECTION("equality") { + Polygon poly1; + Polygon poly2; + + EXPECT(poly1.num_vertices() == 0); + + Polygon::value_type p1 = {1.0, 2.0}; + poly1.push_front(p1); + EXPECT(poly1.num_vertices() == 1); + EXPECT(poly1.vertex(0) == p1); + + EXPECT(!poly1.sameAs(poly2)); + + poly2.push_back(p1); + EXPECT(poly1.sameAs(poly2)); + + Polygon::value_type p2 = {2.0, 1.0}; + poly1.push_front(p2); + EXPECT(!poly1.sameAs(poly2)); + + poly2.push_back(p2); + EXPECT(!poly1.sameAs(poly2)); + } + + + SECTION("congruence") { + Polygon poly1; + Polygon poly2; + EXPECT(poly1.congruent(poly2)); + + Polygon::value_type p1 = {1.0, 2.0}; + poly1.push_front(p1); + EXPECT(!poly1.congruent(poly2)); + + poly2.push_back(p1); + EXPECT(poly1.congruent(poly2)); + + Polygon::value_type p2 = {2.0, 1.0}; + poly1.push_front(p2); + EXPECT(!poly1.congruent(poly2)); + + poly2.push_back(p2); + EXPECT(poly1.congruent(poly2)); + + Polygon::value_type p3 = {3.0, 4.0}; + poly2.push_back(p3); + Polygon poly3 = {p2, p3, p1}; + EXPECT(!poly2.sameAs(poly3)); + EXPECT(poly2.congruent(poly3)); + + EXPECT(poly2.num_vertices() == 3); + EXPECT(poly2.vertex(2) == poly3.vertex(1)); + + Polygon poly4 = {p3, p1, p2}; + EXPECT(!poly2.sameAs(poly4)); + EXPECT(poly2.congruent(poly4)); + } +} + + +CASE("LonLatPolygon") { + using Polygon = geo::polygon::LonLatPolygon; + + + SECTION("Construction") { + const std::vector points1{{0, 0}, {1, 1}, {2, 2}, {0, 0}}; + const std::vector points2{{0, 0}, {1, 0}, {2, 0}, {2, 1}, {2, 2}, + {1, 2}, {0, 2}, {0, 1}, {0, 0}}; + + EXPECT_EQUAL(Polygon(points1).size(), 2); + EXPECT_EQUAL(Polygon(points1.begin(), points1.end()).size(), 2); + + EXPECT_EQUAL(Polygon(points2).size(), 5); + EXPECT_EQUAL(Polygon(points2.begin(), points2.end()).size(), 5); + } + + + SECTION("Contains North pole") { + const std::vector points{{0, 90}, {0, 0}, {1, 0}, {1, 90}, {0, 90}}; + + Polygon poly1(points); + EXPECT(poly1.contains({0, 90})); + EXPECT(poly1.contains({10, 90})); + EXPECT_NOT(poly1.contains({0, -90})); + EXPECT_NOT(poly1.contains({10, -90})); + + Polygon poly2(points, false); + EXPECT(poly2.contains({0, 90})); + EXPECT_NOT(poly2.contains({10, 90})); + EXPECT_NOT(poly2.contains({0, -90})); + EXPECT_NOT(poly2.contains({10, -90})); + } + + + SECTION("Contains South pole") { + const std::vector points{{0, -90}, {0, 0}, {1, 0}, {1, -90}, {0, -90}}; + + Polygon poly1(points); + EXPECT_NOT(poly1.contains({0, 90})); + EXPECT_NOT(poly1.contains({10, 90})); + EXPECT(poly1.contains({0, -90})); + EXPECT(poly1.contains({10, -90})); + + Polygon poly2(points, false); + EXPECT_NOT(poly2.contains({0, 90})); + EXPECT_NOT(poly2.contains({10, 90})); + EXPECT(poly2.contains({0, -90})); + EXPECT_NOT(poly2.contains({10, -90})); + } + + + SECTION("Contains South and North poles") { + Polygon poly({{0, -90}, {0, 90}, {1, 90}, {1, -90}, {0, -90}}); + EXPECT(poly.contains({0, 90})); + EXPECT(poly.contains({10, 90})); + EXPECT(poly.contains({0, 0})); + EXPECT_NOT(poly.contains({10, 0})); + EXPECT(poly.contains({0, -90})); + EXPECT(poly.contains({10, -90})); + } + + + SECTION("MIR-566: wide polygon") { + Polygon poly1({{0, 0}, {361, 0}, {361, 2}, {0, 2}, {0, 0}}); + EXPECT(poly1.contains({0, 1})); + EXPECT(poly1.contains({2, 1})); + EXPECT(poly1.contains({362, 1})); + EXPECT(poly1.contains({722, 1})); + + Polygon poly2({{0, 0}, {11, 0}, {11, 2}, {0, 2}, {0, 0}}); + EXPECT(poly2.contains({0, 1})); + EXPECT(poly2.contains({2, 1})); + EXPECT(poly2.contains({362, 1})); + EXPECT(poly2.contains({722, 1})); + + Polygon poly3({{0, 0}, {360, 0}, {360, 2}, {0, 2}, {0, 0}}); + EXPECT(poly3.contains({0, 1})); + EXPECT(poly3.contains({2 - 360, 1})); + EXPECT(poly3.contains({2, 1})); + EXPECT(poly3.contains({2 + 360, 1})); + + Polygon poly4({{-100, 18}, {21, 30}, {150, 50}, {260, 18}, {-100, 18}}); + EXPECT(poly4.contains({-10 - 360, 18})); + EXPECT(poly4.contains({-10, 18})); + EXPECT(poly4.contains({-10 + 360, 18})); + + Polygon poly5({{-44.2299698513, 44.8732496764}, + {-12.2849279262, 75.2545011911}, + {72.2148603917, 76.7993105902}, + {196.903572422, 71.1350094603}, + {304.194105814, 52.8269579527}, + {266.886210026, -17.7495991714}, + {108.327652927, 34.8499103834}, + {-96.2694736324, -17.4340627522}, + {-99.8761719143, 7.28288763265}, + {-44.2299698513, 44.8732496764}}); + for (double lon = -1, lat = 10; lat < 70; lat += 1) { + EXPECT(poly5.contains({lon - 360, lat})); + EXPECT(poly5.contains({lon, lat})); + EXPECT(poly5.contains({lon + 360, lat})); + } + + constexpr double eps = 0.001; + constexpr double globe = 360; + Polygon poly6({{0 * globe, 4 + eps}, + {1 * globe, 2 + eps}, + {2 * globe, 0 + eps}, + {3 * globe, -2 + eps}, + {4 * globe, -4 + eps}, + {4 * globe, -4 - eps}, + {3 * globe, -2 - eps}, + {2 * globe, 0 - eps}, + {1 * globe, 2 - eps}, + {0 * globe, 4 - eps}, + {0 * globe, 4 + eps}}); + + const std::vector list_lons{-2. * globe, -globe, 0., globe, 2. * globe}; + const std::vector list_lats1{4., 2., 0., -2.}; + const std::vector list_lats2{5., 3., 1., -1., -3., -5.}; + for (double lon : list_lons) { + for (double lat : list_lats1) { + EXPECT(poly6.contains({lon + 180., lat - 1.})); + EXPECT(poly6.contains({lon, lat})); + } + for (double lat : list_lats2) { + EXPECT_NOT(poly6.contains({lon, lat})); + EXPECT_NOT(poly6.contains({lon + 180., lat - 1.})); + } + } + + // HEALPix-like equator wedge in longitude + Polygon poly( + {{0, 1}, {0, 90}, {360, 90}, {360, 1}, {361, 0}, {360, -1}, {360, -90}, {0, -90}, {0, -1}, {1, 0}, {0, 1}}); + EXPECT(poly.contains({0, 0})); + EXPECT(poly.contains({1, 0})); + EXPECT(poly.contains({360, 0})); + EXPECT(poly.contains({361, 0})); + EXPECT(poly.contains({720, 0})); + EXPECT(poly.contains({721, 0})); + } + + + SECTION("MIR-566: winding number strict check of edges") { + Polygon poly({{110, -34}, {90, -62}, {100, -59}, {110, -50}, {132, -40}, {110, -34}}); + EXPECT_NOT(poly.contains({90, -40})); + EXPECT_NOT(poly.contains({90, -34})); + } + + + SECTION("Simple rectangular polygon") { + double lonmin = 0; + double lonmax = 360; + double lonmid = 0.5 * (lonmin + lonmax); + + double latmax = 80; + double latmin = 0; + double latmid = 0.5 * (latmin + latmax); + + Polygon poly({{lonmin, latmax}, {lonmax, latmax}, {lonmax, latmin}, {lonmin, latmin}, {lonmin, latmax}}); + + EXPECT(poly.contains({lonmin, latmax})); + EXPECT(poly.contains({lonmid, latmax})); + EXPECT(poly.contains({lonmax, latmax})); + EXPECT(poly.contains({lonmax, latmid})); + EXPECT(poly.contains({lonmax, latmin})); + EXPECT(poly.contains({lonmid, latmin})); + EXPECT(poly.contains({lonmin, latmin})); + EXPECT(poly.contains({lonmin, latmid})); + + // Test contains in/outward of edges + constexpr auto eps = 0.001; + + for (size_t i = 0; i <= 100; ++i) { + const auto lon = lonmin + static_cast(i) * (lonmax - lonmin) / 100.; + EXPECT(poly.contains({lon, latmin + eps})); + EXPECT(poly.contains({lon, latmax - eps})); + EXPECT_NOT(poly.contains({lon, latmin - eps})); + EXPECT_NOT(poly.contains({lon, latmax + eps})); + + const auto lat = latmin + static_cast(i) * (latmax - latmin) / 100.; + EXPECT(poly.contains({lonmin + eps, lat})); + EXPECT(poly.contains({lonmax - eps, lat})); + EXPECT(poly.contains({lonmin - eps, lat})); + EXPECT(poly.contains({lonmax + eps, lat})); + } + + // Test points at non-canonical coordinates + // Default behavior throws + EXPECT_THROWS_AS(poly.contains({lonmid, 91.}), BadValue); + + auto A = PointLonLat::make(lonmid + 360., latmid, lonmin); + EXPECT(poly.contains({A.lon, A.lat})); + + auto B = PointLonLat::make(lonmid, 180. - latmid, lonmin); + EXPECT(poly.contains({B.lon, B.lat})); + } + + + SECTION("Parallelogram") { + const std::vector points{{0, 0}, {1, 1}, {2, 1}, {1, 0}, {0, 0}}; + Polygon poly(points); + + for (const auto& p : points) { + EXPECT(poly.contains(p)); + } + EXPECT_NOT(poly.contains({0, 1})); + EXPECT_NOT(poly.contains({2, 0})); + } + + + SECTION("Degenerate polygon") { + const std::vector points{{0, 0}, {2, 0}, {2, 0} /*duplicate*/, {0, 2}, {0, 0}}; + + Polygon poly(points); + + for (const auto& p : points) { + EXPECT(poly.contains(p)); + } + + for (const auto& p : std::vector{{2, 2}}) { + EXPECT_NOT(poly.contains(p)); + } + } + + + SECTION("Self-intersecting polygon") { + Polygon poly1({{-1, -1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}}); + + EXPECT(poly1.contains({0, 0})); + EXPECT(poly1.contains({-1, 0})); + EXPECT(poly1.contains({1, 0})); + EXPECT_NOT(poly1.contains({0, 1})); + EXPECT_NOT(poly1.contains({0, -1})); + + Polygon poly2({{-1, -1}, {1, -1}, {-1, 1}, {1, 1}, {-1, -1}}); + + EXPECT(poly2.contains({0, 0})); + EXPECT_NOT(poly2.contains({-1, 0})); + EXPECT_NOT(poly2.contains({1, 0})); + EXPECT(poly2.contains({0, 1})); + EXPECT(poly2.contains({0, -1})); + + Polygon poly3({{-1, 89}, {1, 89}, {0, 90}, {181, 89}, {179, 89}, {0, 90}, {-1, 89}}); + EXPECT(poly3.size() == 7); + + const std::vector list_lons{-720., -360., 0., 360., 720.}; + for (const auto& lon : list_lons) { + EXPECT(poly3.contains({lon, 89.})); + EXPECT(poly3.contains({lon + 180, 89.})); + EXPECT_NOT(poly3.contains({lon + 90, 89.})); + EXPECT_NOT(poly3.contains({lon + 270, 89.})); + } + } + + + SECTION("Partitioning (includePoles=false)") { + auto mid = [](double a, double b) { return (a + b) / 2.; }; + + constexpr double lon[] = {0, 90, 180, 270, 360}; + constexpr double lat[] = {90, 0, -90}; + + Polygon polys[] = { + Polygon({{lon[0], lat[1]}, {lon[1], lat[1]}, {lon[1], lat[0]}, {lon[0], lat[0]}, {lon[0], lat[1]}}, false), + Polygon({{lon[1], lat[1]}, {lon[2], lat[1]}, {lon[2], lat[0]}, {lon[1], lat[0]}, {lon[1], lat[1]}}, false), + Polygon({{lon[2], lat[1]}, {lon[3], lat[1]}, {lon[3], lat[0]}, {lon[2], lat[0]}, {lon[2], lat[1]}}, false), + Polygon({{lon[3], lat[1]}, {lon[4], lat[1]}, {lon[4], lat[0]}, {lon[3], lat[0]}, {lon[3], lat[1]}}, false), + Polygon({{lon[0], lat[1]}, {lon[1], lat[1]}, {lon[1], lat[2]}, {lon[0], lat[2]}, {lon[0], lat[1]}}, false), + Polygon({{lon[1], lat[1]}, {lon[2], lat[1]}, {lon[2], lat[2]}, {lon[1], lat[2]}, {lon[1], lat[1]}}, false), + Polygon({{lon[2], lat[1]}, {lon[3], lat[1]}, {lon[3], lat[2]}, {lon[2], lat[2]}, {lon[2], lat[1]}}, false), + Polygon({{lon[3], lat[1]}, {lon[4], lat[1]}, {lon[4], lat[2]}, {lon[3], lat[2]}, {lon[3], lat[1]}}, false)}; + + + std::vector points; + const std::vector list_lons{lon[0], mid(lon[0], lon[1]), lon[1], mid(lon[1], lon[2]), + lon[2], mid(lon[2], lon[3]), lon[3], mid(lon[3], lon[4])}; + const std::vector list_lats{lat[0], mid(lat[0], lat[1]), lat[1], mid(lat[1], lat[2]), lat[2]}; + + for (double lon : list_lons) { + for (double lat : list_lats) { + points.emplace_back(lon, lat); + } + } + + std::vector counts(points.size(), 0); + for (size_t i = 0; i < points.size(); ++i) { + for (const auto& poly : polys) { + if (poly.contains(points[i])) { + ++counts[i]; + } + } + } + + for (size_t i = 0; i < counts.size(); i += list_lats.size() * 2) { + EXPECT(counts[i + 0] == 2); + EXPECT(counts[i + 1] == 2); + EXPECT(counts[i + 2] == 4); + EXPECT(counts[i + 3] == 2); + EXPECT(counts[i + 4] == 2); + + EXPECT(counts[i + 5] == 1); + EXPECT(counts[i + 6] == 1); + EXPECT(counts[i + 7] == 2); + EXPECT(counts[i + 8] == 1); + EXPECT(counts[i + 9] == 1); + } + } +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/cache.cc b/tests/geo/cache.cc new file mode 100644 index 000000000..082f2758e --- /dev/null +++ b/tests/geo/cache.cc @@ -0,0 +1,109 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Cache.h" +#include "eckit/geo/util.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("eckit::geo::util") { + struct test_t { + size_t N; + bool increasing; + Cache::bytes_size_t pl_footprint; + Cache::bytes_size_t pl_footprint_acc; + Cache::bytes_size_t gl_footprint; + Cache::bytes_size_t gl_footprint_acc; + } tests[] = { + {16, false, 256, 256, 256, 256}, // + {24, false, 384, 640, 384, 640}, // + {24, false, 384, 640, 384, 640}, // (repeated for a cache hit) + {32, false, 512, 1152, 512, 1152}, // + {16, false, 256, 1152, 256, 1152}, // (repeated for another cache hit) + {48, false, 768, 1920, 768, 1920}, // + {16, true, 256, 1920, 256, 2176}, // (repeated except for 'increasing') + {24, true, 384, 1920, 384, 2560}, // ... + {24, true, 384, 1920, 384, 2560}, // + {32, true, 512, 1920, 512, 3072}, // + {16, true, 256, 1920, 256, 3072}, // + {48, true, 768, 1920, 768, 3840}, // + }; + + + Cache::total_purge(); + EXPECT_EQUAL(0, Cache::total_footprint()); + + + SECTION("separate caches") { + util::reduced_classical_pl(16); + auto foot = Cache::total_footprint(); + EXPECT(0 < foot); + + util::reduced_octahedral_pl(16); + EXPECT(foot < Cache::total_footprint()); + } + + + Cache::total_purge(); + EXPECT_EQUAL(0, Cache::total_footprint()); + + + SECTION("reduced_classical_pl, reduced_octahedral_pl") { + for (const geo::pl_type& (*cacheable)(size_t) : {&util::reduced_classical_pl, &util::reduced_octahedral_pl}) { + for (const auto& test : tests) { + Cache::total_purge(); + (*cacheable)(test.N); + EXPECT_EQUAL(Cache::total_footprint(), test.pl_footprint); + } + + Cache::total_purge(); + for (const auto& test : tests) { + (*cacheable)(test.N); + EXPECT_EQUAL(Cache::total_footprint(), test.pl_footprint_acc); + } + } + } + + + Cache::total_purge(); + EXPECT_EQUAL(0, Cache::total_footprint()); + + + SECTION("gaussian_latitudes") { + for (const auto& test : tests) { + Cache::total_purge(); + util::gaussian_latitudes(test.N, test.increasing); + EXPECT_EQUAL(Cache::total_footprint(), test.gl_footprint); + } + + Cache::total_purge(); + for (const auto& test : tests) { + util::gaussian_latitudes(test.N, test.increasing); + EXPECT_EQUAL(Cache::total_footprint(), test.gl_footprint_acc); + } + } + + + Cache::total_purge(); + EXPECT_EQUAL(0, Cache::total_footprint()); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/figure.cc b/tests/geo/figure.cc new file mode 100644 index 000000000..a89eabb43 --- /dev/null +++ b/tests/geo/figure.cc @@ -0,0 +1,86 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/Figure.h" +#include "eckit/geo/figure/Earth.h" +#include "eckit/geo/figure/OblateSpheroid.h" +#include "eckit/geo/figure/Sphere.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/testing/Test.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::test { + + +struct F : std::unique_ptr
{ + explicit F(Figure* ptr) : unique_ptr(ptr) { ASSERT(unique_ptr::operator bool()); } +}; + + +CASE("Sphere") { + F f1(FigureFactory::build(spec::Custom{{"R", 1.}})); + F f2(FigureFactory::build(spec::Custom{{"a", 1.}, {"b", 1.}})); + F f3(new figure::Sphere(1.)); + + EXPECT_THROWS_AS(figure::Sphere(-1.), BadValue); + + EXPECT(*f1 == *f2); + EXPECT(*f1 == *f3); + + auto e = f1->eccentricity(); + EXPECT(types::is_approximately_equal(e, 0.)); + + EXPECT(f1->spec_str() == R"({"r":1})"); +} + + +CASE("Oblate spheroid") { + F f1(FigureFactory::build(spec::Custom{{"b", 0.5}, {"a", 1.}})); + F f2(new figure::OblateSpheroid(1., 0.5)); + + EXPECT_THROWS_AS(figure::OblateSpheroid(0.5, 1.), BadValue); // prolate spheroid + EXPECT_THROWS_AS(figure::OblateSpheroid(1., -1.), BadValue); + + EXPECT(*f1 == *f2); + + auto e = f1->eccentricity(); + EXPECT(types::is_strictly_greater(e, 0.)); + + EXPECT(f1->spec_str() == R"({"a":1,"b":0.5})"); +} + + +CASE("Earth") { + F f1(FigureFactory::build(spec::Custom{{"r", 6371229.}})); + F f2(new figure::Earth); + + EXPECT(*f1 == *f2); + EXPECT(f1->spec_str() == R"({"r":6371229})"); + EXPECT(types::is_approximately_equal(f1->R(), 6371229., 1e-8)); + + F f4(FigureFactory::build(spec::Custom{{"figure", "wgs84"}})); + + EXPECT(f4->spec_str() == R"({"figure":"wgs84"})"); + EXPECT(types::is_approximately_equal(1. / f4->flattening(), 298.257223563, 1e-8)); + EXPECT_THROWS_AS(f4->R(), BadValue); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/geometry_sphere.cc b/tests/geo/geometry_sphere.cc new file mode 100644 index 000000000..d03a070cb --- /dev/null +++ b/tests/geo/geometry_sphere.cc @@ -0,0 +1,275 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/Point3.h" +#include "eckit/geo/PointLonLat.h" +#include "eckit/geo/area/BoundingBox.h" +#include "eckit/geo/geometry/SphereT.h" +#include "eckit/geo/geometry/UnitSphere.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("unit sphere") { + using geometry::UnitSphere; + + const auto R = UnitSphere::radius(); + const auto L = R * std::sqrt(2) / 2.; + + const PointLonLat P1(-71.6, -33.); // Valparaíso + const PointLonLat P2(121.8, 31.4); // Shanghai + + + SECTION("radius") { + EXPECT(UnitSphere::radius() == 1.); + } + + + SECTION("north pole") { + auto p = UnitSphere::convertSphericalToCartesian({0., 90.}); + + EXPECT(p.X == 0); + EXPECT(p.Y == 0); + EXPECT(p.Z == R); + } + + + SECTION("south pole") { + auto p = UnitSphere::convertSphericalToCartesian({0., -90.}); + + EXPECT(p.X == 0); + EXPECT(p.Y == 0); + EXPECT(p.Z == -R); + } + + + SECTION("distances") { + // Same points with added shifts + auto P1b = PointLonLat::make(288.4, -33.); // Valparaíso + longitude shift + auto P2b = PointLonLat::make(301.8, 148.6); // Shanghai + latitude/longitude shift + auto P2c = PointLonLat::make(-58.2, -211.4); // Shanghai + latitude/longitude shift + + auto d0 = UnitSphere::distance(P1, P2); + auto d1 = UnitSphere::distance(P1b, P2); + auto d2 = UnitSphere::distance(P1, P2b); + auto d3 = UnitSphere::distance(P1, P2c); + + EXPECT(types::is_approximately_equal(d0, d1)); + EXPECT(types::is_approximately_equal(d0, d2)); + EXPECT(types::is_approximately_equal(d0, d3)); + } + + + SECTION("area globe") { + EXPECT(UnitSphere::area() == 4. * M_PI * R * R); + } + + + SECTION("area hemispheres") { + auto area_hemisphere_north = UnitSphere::area({90., -180., 0., 180.}); + auto area_hemisphere_south = UnitSphere::area({0., -180., -90., 180.}); + + EXPECT(area_hemisphere_north == 0.5 * UnitSphere::area()); + EXPECT(area_hemisphere_north == area_hemisphere_south); + } + + + SECTION("lon 0 (quadrant)") { + auto p = UnitSphere::convertSphericalToCartesian({0., 0.}); + auto q = UnitSphere::convertSphericalToCartesian({-360., 0.}); + + EXPECT(p.X == R); + EXPECT(p.Y == 0); + EXPECT(p.Z == 0); + + EXPECT(p == q); + } + + + SECTION("lon 90 (quadrant)") { + auto p = UnitSphere::convertSphericalToCartesian({90., 0.}); + auto q = UnitSphere::convertSphericalToCartesian({-270., 0.}); + + EXPECT(p.X == 0); + EXPECT(p.Y == R); + EXPECT(p.Z == 0); + + EXPECT(p == q); + } + + + SECTION("lon 180 (quadrant)") { + auto p = UnitSphere::convertSphericalToCartesian({180., 0.}); + auto q = UnitSphere::convertSphericalToCartesian({-180., 0.}); + + EXPECT(p.X == -R); + EXPECT(p.Y == 0); + EXPECT(p.Z == 0); + + EXPECT(p == q); + } + + + SECTION("lon 270 (quadrant)") { + auto p = UnitSphere::convertSphericalToCartesian({270., 0.}); + auto q = UnitSphere::convertSphericalToCartesian({-90., 0.}); + + EXPECT(p.X == 0); + EXPECT(p.Y == -R); + EXPECT(p.Z == 0); + + EXPECT(p == q); + } + + + SECTION("lon 45 (octant)") { + auto p = UnitSphere::convertSphericalToCartesian({45., 0.}); + auto q = UnitSphere::convertSphericalToCartesian({-315., 0.}); + + EXPECT(types::is_approximately_equal(p.X, L)); + EXPECT(types::is_approximately_equal(p.Y, L)); + EXPECT(p.Z == 0); + + EXPECT(p == q); + } + + + SECTION("lon 135 (octant)") { + auto p = UnitSphere::convertSphericalToCartesian({135., 0.}); + auto q = UnitSphere::convertSphericalToCartesian({-225., 0.}); + + EXPECT(types::is_approximately_equal(p.X, -L)); + EXPECT(types::is_approximately_equal(p.Y, L)); + EXPECT(p.Z == 0); + + EXPECT(p == q); + } + + + SECTION("lon 225 (octant)") { + auto p = UnitSphere::convertSphericalToCartesian({225., 0.}); + auto q = UnitSphere::convertSphericalToCartesian({-135., 0.}); + + EXPECT(types::is_approximately_equal(p.X, -L)); + EXPECT(types::is_approximately_equal(p.Y, -L)); + EXPECT(p.Z == 0); + + EXPECT(p == q); + } + + + SECTION("lon 315 (octant)") { + auto p = UnitSphere::convertSphericalToCartesian({315., 0.}); + auto q = UnitSphere::convertSphericalToCartesian({-45., 0.}); + + EXPECT(types::is_approximately_equal(p.X, L)); + EXPECT(types::is_approximately_equal(p.Y, -L)); + EXPECT(p.Z == 0); + + EXPECT(p == q); + } + + + SECTION("lat 100") { + // Default behavior throws + EXPECT_THROWS_AS(PointLonLat::assert_latitude_range(PointLonLat(0., 100.)), BadValue); + + auto p = UnitSphere::convertSphericalToCartesian(PointLonLat::make(0., 100.), 0.); + auto q = UnitSphere::convertSphericalToCartesian(PointLonLat::make(180., 80.), 0.); + + // sin(x) and sin(pi-x) are not bitwise identical + EXPECT(types::is_approximately_equal(p.X, q.X)); + EXPECT(types::is_approximately_equal(p.Y, q.Y)); + EXPECT(types::is_approximately_equal(p.Z, q.Z)); + } + + + SECTION("lat 290") { + // Default behavior throws + EXPECT_THROWS_AS(PointLonLat::assert_latitude_range(PointLonLat(15., 290.)), BadValue); + + auto p = UnitSphere::convertSphericalToCartesian(PointLonLat::make(15., 290.), 0.); + auto q = UnitSphere::convertSphericalToCartesian(PointLonLat::make(15., -70.), 0.); + + // sin(x) and sin(pi-x) are not bitwise identical + EXPECT(types::is_approximately_equal(p.X, q.X)); + EXPECT(types::is_approximately_equal(p.Y, q.Y)); + EXPECT(types::is_approximately_equal(p.Z, q.Z)); + } + + + SECTION("lat -120") { + // Default behavior throws + EXPECT_THROWS_AS(PointLonLat::assert_latitude_range(PointLonLat(45., -120.)), BadValue); + + auto p = UnitSphere::convertSphericalToCartesian(PointLonLat::make(45., -120.), 0.); + auto q = UnitSphere::convertSphericalToCartesian(PointLonLat::make(225., -60.), 0.); + + // sin(x) and sin(pi-x) are not bitwise identical + EXPECT(types::is_approximately_equal(p.X, q.X)); + EXPECT(types::is_approximately_equal(p.Y, q.Y)); + EXPECT(types::is_approximately_equal(p.Z, q.Z)); + } +} + + +CASE("two-unit sphere") { + struct DatumTwoUnits { + static double radius() { return 2.; } + }; + + using geometry::UnitSphere; + using TwoUnitsSphere = geometry::SphereT; + + const PointLonLat P1(-71.6, -33.); // Valparaíso + const PointLonLat P2(121.8, 31.4); // Shanghai + + + SECTION("radius") { + EXPECT(TwoUnitsSphere::radius() == 2.); + } + + + SECTION("distances") { + auto distance_1 = UnitSphere::distance(P1, P2); + auto distance_2 = TwoUnitsSphere::distance(P1, P2); + EXPECT(2. * distance_1 == distance_2); + } + + + SECTION("area globe") { + auto area_1 = UnitSphere::area(); + auto area_2 = TwoUnitsSphere::area(); + EXPECT(4. * area_1 == area_2); + } + + + SECTION("sub areas") { + area::BoundingBox bbox({P2.lat, P1.lon, P1.lat, P2.lon}); + + auto area_1 = UnitSphere::area(bbox); + auto area_2 = TwoUnitsSphere::area(bbox); + EXPECT(4. * area_1 == area_2); + } +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/great_circle.cc b/tests/geo/great_circle.cc new file mode 100644 index 000000000..283f4da2f --- /dev/null +++ b/tests/geo/great_circle.cc @@ -0,0 +1,233 @@ +/* + * (C) Copyright 1996- ECMWF. + * (C) Crown Copyright 2023 Met Office. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include +#include + +#include "eckit/geo/GreatCircle.h" +#include "eckit/geo/PointLonLat.h" +#include "eckit/testing/Test.h" + +#define EXPECT_APPROX(a, b, eps) EXPECT(::eckit::types::is_approximately_equal((a), (b), (eps))) + + +namespace eckit::geo::test { + + +const PointLonLat VALPARAISO(-71.6, -33.); +const PointLonLat SHANGHAI(121.8, 31.4); + + +CASE("great circle intersections") { + using types::is_approximately_equal; + using types::is_approximately_greater_or_equal; + + auto is_approximately_equal_longitude + = [](double lon1, double lon2, double epsilon = std::numeric_limits::epsilon()) -> bool { + while (lon2 < lon1) { + lon2 += 360; + } + while (lon1 >= lon1 + 360) { + lon2 -= 360; + } + return is_approximately_equal(lon1, lon2, epsilon) || is_approximately_equal(lon1, lon2 - 360, epsilon); + }; + + auto is_approximately_pole = [](double lat, double epsilon = std::numeric_limits::epsilon()) -> bool { + return is_approximately_equal(std::abs(lat), 90., epsilon); + }; + + auto is_approximately_equator = [](double lat, double epsilon = std::numeric_limits::epsilon()) -> bool { + return is_approximately_equal(lat, 0., epsilon); + }; + + const std::vector latitudes{ + 90, 60, 45, 30, 0, -30, -45, -60, -90, + }; + + const std::vector longitudes{ + -181, -180, -135, -90, -45, 0, 45, 90, 135, 180, 225, 270, 315, 360, 361, + }; + + const std::vector antipodes{ + {0, 0}, {180, 0}, {-180, 0}, {0, 0}, {-90, 0}, {90, 0}, {90, 0}, + {-90, 0}, {0, 90}, {0, -90}, {0, -90}, {0, 90}, {45, 45}, {225, -45}, + }; + + SECTION("example intersection with meridian and parallel") { + // latitude at Valparaíso-Shanghai mid-point + GreatCircle gc(VALPARAISO, SHANGHAI); + + const PointLonLat mid(-159.18, -6.81); + + auto lats = gc.latitude(mid.lon); + EXPECT(lats.size() == 1 && is_approximately_equal(lats[0], mid.lat, 0.01)); + + auto lons = gc.longitude(mid.lat); + EXPECT(lons.size() == 2); + EXPECT(is_approximately_equal_longitude(lons[0], mid.lon, 0.01) + || is_approximately_equal_longitude(lons[1], mid.lon, 0.01)); + } + + SECTION("mal-formed great circle") { + for (size_t i = 0; i < antipodes.size(); i += 2) { + const PointLonLat& A(antipodes[i]); + const PointLonLat& B(antipodes[i + 1]); + + EXPECT_THROWS_AS(GreatCircle(A, A), BadValue); + EXPECT_THROWS_AS(GreatCircle(B, B), BadValue); + + EXPECT_THROWS_AS(GreatCircle(A, B), BadValue); + + if (is_approximately_pole(A.lat)) { + for (double lon1_gc : longitudes) { + for (double lon2_gc : longitudes) { + EXPECT_THROWS_AS(GreatCircle({lon1_gc, A.lat}, {lon2_gc, A.lat}), BadValue); + EXPECT_THROWS_AS(GreatCircle({lon1_gc, B.lat}, {lon2_gc, B.lat}), BadValue); + } + } + } + } + } + + SECTION("intersection at quadrants") { + for (double lat_gc : latitudes) { + if (!is_approximately_pole(lat_gc) && !is_approximately_equator(lat_gc)) { + for (double lon_gc : longitudes) { + + GreatCircle gc({lon_gc, lat_gc}, {lon_gc + 90, 0}); + EXPECT(!gc.crossesPoles()); + + auto lon_at_equator = gc.longitude(0); + EXPECT(lon_at_equator.size() == 2); + EXPECT((is_approximately_equal_longitude(lon_gc + 90, lon_at_equator[0]) + && is_approximately_equal_longitude(lon_gc - 90, lon_at_equator[1])) + || (is_approximately_equal_longitude(lon_gc - 90, lon_at_equator[0]) + && is_approximately_equal_longitude(lon_gc + 90, lon_at_equator[1]))); + + auto lon_extrema1 = gc.longitude(lat_gc); + EXPECT(lon_extrema1.size() == 1 && is_approximately_equal_longitude(lon_extrema1[0], lon_gc, 0.01)); + + auto lon_extrema2 = gc.longitude(-lat_gc); + EXPECT(lon_extrema2.size() == 1 + && is_approximately_equal_longitude(lon_extrema2[0], lon_gc + 180, 0.01)); + } + } + } + } + + SECTION("intersection with parallels when crossing the poles") { + for (double lon : longitudes) { + for (double lat : latitudes) { + + { + GreatCircle gc({lon, -10}, {lon, 10}); + EXPECT(gc.crossesPoles()); + + auto lons = gc.longitude(lat); + size_t N = is_approximately_pole(lat) ? 1 : 2; + EXPECT(lons.size() == N); + + if (N == 1) { + EXPECT(is_approximately_equal_longitude(lons[0], lon)); + } + else { + EXPECT(is_approximately_equal_longitude(lons[0] + 180, lons[1])); + EXPECT(is_approximately_equal_longitude(lons[0], lon) + || is_approximately_equal_longitude(lons[1], lon)); + } + } + + if (!is_approximately_pole(lat) && !is_approximately_equator(lat)) { + GreatCircle gc({lon, lat}, {lon + 180, lat}); + EXPECT(gc.crossesPoles()); + + auto lons = gc.longitude(lat); + EXPECT(lons.size() == 2); + + EXPECT(is_approximately_equal_longitude(lons[0] + 180, lons[1])); + EXPECT(is_approximately_equal_longitude(lons[0], lon) + || is_approximately_equal_longitude(lons[1], lon)); + } + } + } + } + + SECTION("intersection with parallels") { + for (double lat_gc : latitudes) { + if (/* avoid mal-forming */ !is_approximately_pole(lat_gc)) { + for (double lat : latitudes) { + + GreatCircle gc({-1, lat_gc}, {1, lat_gc}); + EXPECT(!gc.crossesPoles()); + + auto lons = gc.longitude(lat); + size_t N = is_approximately_equator(lat_gc) ? 0 + : is_approximately_greater_or_equal(std::abs(lat_gc), std::abs(lat)) ? 2 + : 0; + EXPECT(lons.size() == N); + + for (auto lon : lons) { + auto lats = gc.latitude(lon); + EXPECT(lats.size() == 1 && is_approximately_equal(lats[0], lat, 0.01)); + } + } + } + } + } + + SECTION("equator great circle intersection with meridian and parallel") { + for (double lon : longitudes) { + + GreatCircle eq({lon - 1, 0}, {lon + 1, 0}); + EXPECT(!eq.crossesPoles()); + + // non-intersection with parallels + for (double lat : latitudes) { + EXPECT(eq.longitude(lat).empty()); + } + + // intersect one latitude only, for specific longitudes + auto lats = eq.latitude(lon); + EXPECT(lats.size() == 1 && is_approximately_equator(lats[0])); + } + } +} + + +CASE("great circle course") { + SECTION("Valparaíso-Shanghai") { + GreatCircle gc(VALPARAISO, SHANGHAI); + const auto [course1, course2] = gc.course(); + + EXPECT_APPROX(-94.41, course1, 0.01); + EXPECT_APPROX(-78.42, course2, 0.01); + } + + SECTION("polar") { + GreatCircle gc({0., 89.}, {180., 89.}); + const auto [course3, course4] = gc.course(); + + EXPECT_APPROX(0., course3, 1.e-14); + EXPECT_APPROX(180., std::abs(course4), 1.e-14); + } +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/grid.cc b/tests/geo/grid.cc new file mode 100644 index 000000000..3101ee292 --- /dev/null +++ b/tests/geo/grid.cc @@ -0,0 +1,76 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/Cache.h" +#include "eckit/geo/Grid.h" +#include "eckit/geo/LibEcKitGeo.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/geo/util.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("GridFactory::build") { + SECTION("GridFactory::build_from_name") { + struct { + std::string name; + size_t size; + } tests[]{{"O2", 88}, {"f2", 32}, {"h2", 48}}; + + for (const auto& test : tests) { + std::unique_ptr grid(GridFactory::build(spec::Custom({{"grid", test.name}}))); + + auto size = grid->size(); + EXPECT_EQUAL(size, test.size); + } + } + + + SECTION("Grid::build_from_increments (global)") { + std::unique_ptr global(GridFactory::build(spec::Custom({ + {"type", "regular_ll"}, + {"west_east_increment", 1}, + {"south_north_increment", 1}, + }))); + + auto size = global->size(); + EXPECT_EQUAL(size, 360 * 181); + } + + + SECTION("Grid::build_from_increments (non-global)") { + std::unique_ptr grid(GridFactory::build(spec::Custom({ + {"type", "regular_ll"}, + {"west_east_increment", 1}, + {"south_north_increment", 1}, + {"north", 10}, + {"west", 1}, + {"south", 1}, + {"east", 10}, + }))); + + auto size = grid->size(); + EXPECT_EQUAL(size, 100); + } +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/grid_healpix.cc b/tests/geo/grid_healpix.cc new file mode 100644 index 000000000..0ca67dcf1 --- /dev/null +++ b/tests/geo/grid_healpix.cc @@ -0,0 +1,191 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to itr by virtue of its status as an intergovernmental organisation nor + * does itr submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/grid/HEALPix.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("gridspec") { + spec::Custom spec({{"grid", "h2"}}); + std::unique_ptr grid1(GridFactory::build(spec)); + auto n1 = grid1->size(); + + EXPECT_EQUAL(n1, 48); +} + + +CASE("sizes") { + struct test_t { + explicit test_t(size_t N) : N(N), size(12 * N * N) {} + size_t N; + size_t size; + } tests[]{test_t{2}, test_t{3}, test_t{64}}; + + for (const auto& test : tests) { + std::unique_ptr grid1(GridFactory::build(spec::Custom({{"grid", "h" + std::to_string(test.N)}}))); + std::unique_ptr grid2(GridFactory::build(spec::Custom({{"type", "HEALPix"}, {"Nside", test.N}}))); + grid::HEALPix grid3(test.N); + + EXPECT(grid1->size() == test.size); + EXPECT(grid2->size() == test.size); + EXPECT(grid3.size() == test.size); + } +} + + +CASE("points") { + + std::unique_ptr ring(new grid::HEALPix(2)); + + EXPECT(ring->ordering() == Ordering::healpix_ring); + + + std::unique_ptr nested(new grid::HEALPix(2, Ordering::healpix_nested)); + + EXPECT(nested->ordering() == Ordering::healpix_nested); + + // reference coordinates in ring ordering + const std::vector ref{ + {45., 66.443535691}, + {135., 66.443535691}, + {225., 66.443535691}, + {315., 66.443535691}, + {22.5, 41.810314896}, + {67.5, 41.810314896}, + {112.5, 41.810314896}, + {157.5, 41.810314896}, + {202.5, 41.810314896}, + {247.5, 41.810314896}, + {292.5, 41.810314896}, + {337.5, 41.810314896}, + {0., 19.471220634}, + {45., 19.471220634}, + {90., 19.471220634}, + {135., 19.471220634}, + {180., 19.471220634}, + {225., 19.471220634}, + {270., 19.471220634}, + {315., 19.471220634}, + {22.5, 0.}, + {67.5, 0.}, + {112.5, 0.}, + {157.5, 0.}, + {202.5, 0.}, + {247.5, 0.}, + {292.5, 0.}, + {337.5, 0.}, + {0., -19.471220634}, + {45., -19.471220634}, + {90., -19.471220634}, + {135., -19.471220634}, + {180., -19.471220634}, + {225., -19.471220634}, + {270., -19.471220634}, + {315., -19.471220634}, + {22.5, -41.810314896}, + {67.5, -41.810314896}, + {112.5, -41.810314896}, + {157.5, -41.810314896}, + {202.5, -41.810314896}, + {247.5, -41.810314896}, + {292.5, -41.810314896}, + {337.5, -41.810314896}, + {45., -66.443535691}, + {135., -66.443535691}, + {225., -66.443535691}, + {315., -66.443535691}, + }; + + + auto points_r = ring->to_points(); + + EXPECT(points_r.size() == ring->size()); + ASSERT(points_r.size() == ref.size()); + + auto itr = ring->begin(); + for (size_t i = 0; i < points_r.size(); ++i) { + EXPECT(points_equal(ref[i], points_r[i])); + EXPECT(points_equal(ref[i], *itr)); + ++itr; + } + + EXPECT(itr == ring->end()); + + size_t i = 0; + for (const auto& it : *ring) { + EXPECT(points_equal(ref[i++], it)); + } + + EXPECT(i == ring->size()); + + + auto ren = nested->reorder(Ordering::healpix_ring); + auto points_n = nested->to_points(); + + EXPECT(points_n.size() == nested->size()); + ASSERT(points_n.size() == ref.size()); + + auto it = nested->begin(); + for (size_t i = 0; i < points_n.size(); ++i) { + EXPECT(points_equal(ref[ren.at(i)], points_n[i])); + EXPECT(points_equal(ref[ren.at(i)], *it)); + ++it; + } + + EXPECT(it == nested->end()); + + size_t j = 0; + for (const auto& it : *nested) { + EXPECT(points_equal(ref[ren.at(j++)], it)); + } + + EXPECT(i == nested->size()); +} + + +CASE("equals") { + std::unique_ptr grid1(GridFactory::build(spec::Custom({{"grid", "h2"}}))); + std::unique_ptr grid2(GridFactory::make_from_string("{type: HEALPix, Nside: 2}")); + std::unique_ptr grid3(new grid::HEALPix(2)); + + EXPECT(*grid1 == *grid2); + EXPECT(*grid2 == *grid3); + EXPECT(*grid3 == *grid1); + + EXPECT(grid1->ordering() == Ordering::healpix_ring); + + std::unique_ptr grid4(GridFactory::build(spec::Custom({{"grid", "h2"}, {"ordering", "nested"}}))); + std::unique_ptr grid5(GridFactory::make_from_string("{type: HEALPix, Nside: 2, ordering: nested}")); + std::unique_ptr grid6(new grid::HEALPix(2, Ordering::healpix_nested)); + + EXPECT(*grid4 != *grid1); + + EXPECT(*grid4 == *grid5); + EXPECT(*grid5 == *grid6); + EXPECT(*grid6 == *grid4); + + EXPECT(grid4->ordering() == Ordering::healpix_nested); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/grid_orca.cc b/tests/geo/grid_orca.cc new file mode 100644 index 000000000..b4be928ef --- /dev/null +++ b/tests/geo/grid_orca.cc @@ -0,0 +1,108 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to itr by virtue of its status as an intergovernmental organisation nor + * does itr submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/Cache.h" +#include "eckit/geo/LibEcKitGeo.h" +#include "eckit/geo/grid/ORCA.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +static const Grid::uid_t uid = "d5bde4f52ff3a9bea5629cd9ac514410"; +static const std::vector dimensions{182, 149}; + + +CASE("caching") { + if (LibEcKitGeo::caching()) { + SECTION("Grid::build_from_uid") { + spec::Custom spec({{"uid", uid}}); + + const auto footprint_1 = Cache::total_footprint(); + + std::unique_ptr grid1(GridFactory::build(spec)); + + const auto footprint_2 = Cache::total_footprint(); + EXPECT(footprint_1 < footprint_2); + + std::unique_ptr grid2(GridFactory::build(spec)); + + EXPECT(footprint_2 == Cache::total_footprint()); + + EXPECT(grid1->size() == grid2->size()); + } + } +} + + +CASE("spec") { + std::unique_ptr spec(GridFactory::make_spec(spec::Custom({{"uid", uid}}))); + + EXPECT(spec->get_string("type") == "ORCA"); + EXPECT(spec->get_string("orca_name") == "ORCA2"); + EXPECT(spec->get_string("orca_arrangement") == "T"); + EXPECT(spec->get_string("orca_uid") == uid); + EXPECT(spec->get_long_vector("dimensions") == dimensions); + + std::unique_ptr grid1(GridFactory::make_from_string("{uid:" + uid + "}")); + + EXPECT(grid1->size() == dimensions[0] * dimensions[1]); + EXPECT(grid1->uid() == uid); + + std::unique_ptr grid2(GridFactory::build(spec::Custom({{"uid", uid}}))); + + EXPECT(grid2->size() == dimensions[0] * dimensions[1]); + EXPECT(grid2->uid() == uid); + + grid::ORCA grid3(uid); + + const std::string expected_spec_str = R"({"type":"ORCA","uid":")" + uid + R"("})"; + + EXPECT(grid3.uid() == uid); + EXPECT(grid3.calculate_uid() == uid); + EXPECT(static_cast(grid3).spec_str() == expected_spec_str); + + EXPECT(grid1->spec_str() == grid2->spec_str()); + + std::unique_ptr grid4(GridFactory::build(spec::Custom({{"grid", "ORCA2_T"}}))); + + EXPECT(grid4->spec_str() == expected_spec_str); + + std::unique_ptr grid5(GridFactory::build(spec::Custom({{"uid", uid}}))); + + EXPECT(*grid4 == *grid5); +} + + +CASE("equals") { + std::unique_ptr grid1(GridFactory::make_from_string("{uid:" + uid + "}")); + std::unique_ptr grid2(GridFactory::build(spec::Custom({{"uid", uid}}))); + std::unique_ptr grid3(GridFactory::build(spec::Custom({{"grid", uid}}))); + grid::ORCA grid4(uid); + + EXPECT(*grid1 == *grid2); + EXPECT(*grid2 == *grid3); + EXPECT(*grid3 == grid4); + EXPECT(grid4 == *grid1); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/grid_reduced_gg.cc b/tests/geo/grid_reduced_gg.cc new file mode 100644 index 000000000..f2278a0a2 --- /dev/null +++ b/tests/geo/grid_reduced_gg.cc @@ -0,0 +1,182 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/grid/ReducedGaussian.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/geo/util.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +using ReducedGaussian = grid::ReducedGaussian; + + +CASE("gridspec") { + // different ways to instantiate the same grid (O2) + for (const auto& spec : { + spec::Custom({{"grid", "o2"}}), + spec::Custom({{"N", 2}}), + spec::Custom({{"pl", pl_type{20, 24, 24, 20}}}), + }) { + std::unique_ptr grid1(GridFactory::build(spec)); + auto n1 = grid1->size(); + + EXPECT_EQUAL(n1, 88); + + spec::Custom hemisphere(spec.container()); + hemisphere.set("south", 0); + + std::unique_ptr grid2(GridFactory::build(hemisphere)); + auto n2 = grid2->size(); + + EXPECT_EQUAL(n2, n1 / 2); + } +} + + +CASE("sizes") { + struct test_t { + explicit test_t(size_t N) : N(N), size(4 * N * (N + 9)) {} + size_t N; + size_t size; + } tests[]{test_t{2}, test_t{3}, test_t{64}}; + + for (const auto& test : tests) { + std::unique_ptr grid1(GridFactory::build(spec::Custom({{"grid", "o" + std::to_string(test.N)}}))); + std::unique_ptr grid2(GridFactory::build(spec::Custom({{"type", "reduced_gg"}, {"N", test.N}}))); + ReducedGaussian grid3(test.N); + + EXPECT(grid1->size() == test.size); + EXPECT(grid2->size() == test.size); + EXPECT(grid3.size() == test.size); + } +} + + +CASE("points") { + ReducedGaussian grid(1); + + const std::vector ref{ + {0., 35.264389683}, {18., 35.264389683}, {36., 35.264389683}, {54., 35.264389683}, + {72., 35.264389683}, {90., 35.264389683}, {108., 35.264389683}, {126., 35.264389683}, + {144., 35.264389683}, {162., 35.264389683}, {180., 35.264389683}, {198., 35.264389683}, + {216., 35.264389683}, {234., 35.264389683}, {252., 35.264389683}, {270., 35.264389683}, + {288., 35.264389683}, {306., 35.264389683}, {324., 35.264389683}, {342., 35.264389683}, + {0., -35.264389683}, {18., -35.264389683}, {36., -35.264389683}, {54., -35.264389683}, + {72., -35.264389683}, {90., -35.264389683}, {108., -35.264389683}, {126., -35.264389683}, + {144., -35.264389683}, {162., -35.264389683}, {180., -35.264389683}, {198., -35.264389683}, + {216., -35.264389683}, {234., -35.264389683}, {252., -35.264389683}, {270., -35.264389683}, + {288., -35.264389683}, {306., -35.264389683}, {324., -35.264389683}, {342., -35.264389683}, + }; + + auto points = grid.to_points(); + + EXPECT(points.size() == grid.size()); + ASSERT(points.size() == ref.size()); + + auto it = grid.begin(); + for (size_t i = 0; i < points.size(); ++i) { + EXPECT(points_equal(ref[i], points[i])); + EXPECT(points_equal(ref[i], *it)); + ++it; + } + EXPECT(it == grid.end()); + + size_t i = 0; + for (const auto& it : grid) { + EXPECT(points_equal(ref[i++], it)); + } + EXPECT(i == grid.size()); +} + + +CASE("crop") { + spec::Custom a({{"grid", "o2"}}); + std::unique_ptr grid1(GridFactory::build(a)); + auto n1 = grid1->size(); + + EXPECT_EQUAL(n1, 88); + + a.set("south", 0.); + std::unique_ptr grid2(GridFactory::build(a)); + auto n2 = grid2->size(); + + EXPECT_EQUAL(n2, n1 / 2); + + spec::Custom b{{{"grid", "o2"}, {"west", -180}}}; + std::unique_ptr grid3(GridFactory::build(b)); + auto n3 = grid3->size(); + + EXPECT_EQUAL(n3, n1); + + EXPECT(grid3->boundingBox().periodic()); + + // (exclude Greenwhich meridian) + std::unique_ptr grid4(grid3->make_grid_cropped(area::BoundingBox(90., -180., 0., -1.e-6))); + + auto n4 = grid4->size(); + + EXPECT_EQUAL(n4, n3 / 4); + + const std::vector ref{ + {-180., 59.444408289}, {-162., 59.444408289}, {-144., 59.444408289}, {-126., 59.444408289}, + {-108., 59.444408289}, {-90., 59.444408289}, {-72., 59.444408289}, {-54., 59.444408289}, + {-36., 59.444408289}, {-18., 59.444408289}, {-180., 19.875719147}, {-165., 19.875719147}, + {-150., 19.875719147}, {-135., 19.875719147}, {-120., 19.875719147}, {-105., 19.875719147}, + {-90., 19.875719147}, {-75., 19.875719147}, {-60., 19.875719147}, {-45., 19.875719147}, + {-30., 19.875719147}, {-15., 19.875719147}, + }; + + auto points4 = grid4->to_points(); + + EXPECT(points4.size() == n4); + ASSERT(points4.size() == ref.size()); + + auto it = grid4->begin(); + for (size_t i = 0; i < points4.size(); ++i) { + EXPECT(points_equal(ref[i], points4[i])); + EXPECT(points_equal(ref[i], *it)); + ++it; + } + EXPECT(it == grid4->end()); + + size_t i = 0; + for (const auto& it : *grid4) { + EXPECT(points_equal(ref[i++], it)); + } + EXPECT_EQUAL(i, n4); +} + + +CASE("equals") { + std::unique_ptr grid1(GridFactory::build(spec::Custom({{"grid", "o3"}}))); + std::unique_ptr grid2(GridFactory::make_from_string("N: 3")); + std::unique_ptr grid3(new ReducedGaussian(3)); + std::unique_ptr grid4(new ReducedGaussian(pl_type{20, 24, 28, 28, 24, 20})); + + EXPECT(*grid1 == *grid2); + EXPECT(*grid2 == *grid3); + EXPECT(*grid3 == *grid4); + EXPECT(*grid4 == *grid1); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/grid_regular_gg.cc b/tests/geo/grid_regular_gg.cc new file mode 100644 index 000000000..9f9728bec --- /dev/null +++ b/tests/geo/grid_regular_gg.cc @@ -0,0 +1,155 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/Grid.h" +#include "eckit/geo/grid/RegularGaussian.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/geo/util.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +using RegularGaussian = grid::RegularGaussian; + + +CASE("sizes") { + struct test_t { + explicit test_t(size_t N) : N(N), size(4 * N * 2 * N) {} + size_t N; + size_t size; + } tests[]{test_t{2}, test_t{3}, test_t{64}}; + + for (const auto& test : tests) { + std::unique_ptr grid1(GridFactory::build(spec::Custom({{"grid", "f" + std::to_string(test.N)}}))); + std::unique_ptr grid2(GridFactory::build(spec::Custom({{"type", "regular_gg"}, {"N", test.N}}))); + RegularGaussian grid3(test.N); + + EXPECT(grid1->size() == test.size); + EXPECT(grid2->size() == test.size); + EXPECT(grid3.size() == test.size); + } +} + + +CASE("points") { + RegularGaussian grid(1); + + const std::vector ref{ + {0., 35.264389683}, {90., 35.264389683}, {180., 35.264389683}, {270., 35.264389683}, + {0., -35.264389683}, {90., -35.264389683}, {180., -35.264389683}, {270., -35.264389683}, + }; + + auto points = grid.to_points(); + + EXPECT(points.size() == grid.size()); + ASSERT(points.size() == ref.size()); + + auto it = grid.begin(); + for (size_t i = 0; i < points.size(); ++i) { + EXPECT(points_equal(ref[i], points[i])); + EXPECT(points_equal(ref[i], *it)); + ++it; + } + EXPECT(it == grid.end()); + + size_t i = 0; + for (const auto& it : grid) { + EXPECT(points_equal(ref[i++], it)); + } + EXPECT(i == grid.size()); +} + + +CASE("crop") { + spec::Custom a({{"grid", "f2"}}); + std::unique_ptr grid1(GridFactory::build(a)); + auto n1 = grid1->size(); + + EXPECT_EQUAL(n1, 32); + + a.set("south", 0.); + std::unique_ptr grid2(GridFactory::build(a)); + auto n2 = grid2->size(); + + EXPECT_EQUAL(n2, n1 / 2); + + spec::Custom b{{{"grid", "f2"}, {"west", -180}}}; + std::unique_ptr grid3(GridFactory::build(b)); + auto n3 = grid3->size(); + + EXPECT_EQUAL(n3, n1); + + auto bbox3 = grid3->boundingBox(); + + EXPECT(bbox3.periodic()); + + bbox3 = {bbox3.north, bbox3.west, bbox3.south, 0.}; + + EXPECT_NOT(bbox3.periodic()); + + std::unique_ptr grid4(grid3->make_grid_cropped(bbox3)); + auto n4 = grid4->size(); + + EXPECT_EQUAL(n4, 5 * 4); // Ni * Nj + + b.set("east", -1.); + std::unique_ptr grid5(GridFactory::build(b)); + auto n5 = grid5->size(); + + EXPECT_EQUAL(n5, 4 * 4); // Ni * Nj + + const std::vector ref{ + {-180., 59.444408289}, {-135., 59.444408289}, {-90., 59.444408289}, {-45., 59.444408289}, + {-180., 19.875719147}, {-135., 19.875719147}, {-90., 19.875719147}, {-45., 19.875719147}, + {-180., -19.875719147}, {-135., -19.875719147}, {-90., -19.875719147}, {-45., -19.875719147}, + {-180., -59.444408289}, {-135., -59.444408289}, {-90., -59.444408289}, {-45., -59.444408289}, + }; + + auto points5 = grid5->to_points(); + + EXPECT(points5.size() == grid5->size()); + ASSERT(points5.size() == ref.size()); + + auto it = grid5->begin(); + for (size_t i = 0; i < points5.size(); ++i) { + EXPECT(points_equal(ref[i], points5[i])); + EXPECT(points_equal(ref[i], *it)); + ++it; + } + EXPECT(it == grid5->end()); + + size_t i = 0; + for (const auto& it : *grid5) { + EXPECT(points_equal(ref[i++], it)); + } + EXPECT_EQUAL(i, n5); +} + + +CASE("equals") { + std::unique_ptr grid1(GridFactory::build(spec::Custom({{"grid", "f3"}}))); + std::unique_ptr grid2(new RegularGaussian(3)); + + EXPECT(*grid1 == *grid2); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/grid_regular_ll.cc b/tests/geo/grid_regular_ll.cc new file mode 100644 index 000000000..2b7168aa0 --- /dev/null +++ b/tests/geo/grid_regular_ll.cc @@ -0,0 +1,84 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/grid/RegularLL.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +using RegularLL = grid::RegularLL; + + +CASE("global") { + std::unique_ptr grid1(new RegularLL(spec::Custom{{{"grid", std::vector{1, 1}}}})); + + EXPECT(grid1->size() == 360 * 181); + + std::unique_ptr grid2(new RegularLL( + spec::Custom{{{"grid", std::vector{2, 1}}, {"area", std::vector{10, 1, 1, 10}}}})); + + EXPECT(grid2->size() == 5 * 10); + + for (const auto& grid : {RegularLL({1., 1.}, {89.5, 0.5, -89.5, 359.5}), + RegularLL({1., 1.}, {90., 0., -90, 360.}, nullptr, {0.5, 0.5})}) { + EXPECT(grid.nx() == 360); + EXPECT(grid.ny() == 180); + EXPECT(grid.size() == 360 * 180); + } +} + + +CASE("non-global") { + /* + * 1 . . . . + * 0 + * -1 . . . . + * -1 0 1 2 + */ + RegularLL grid({1, 2}, {1, -1, -1, 2}); + + const std::vector ref{ + {-1., 1.}, {0., 1.}, {1., 1.}, {2., 1.}, {-1., -1.}, {0., -1.}, {1., -1.}, {2., -1.}, + }; + + auto points = grid.to_points(); + + EXPECT(points.size() == grid.size()); + ASSERT(points.size() == ref.size()); + + auto it = grid.begin(); + for (size_t i = 0; i < points.size(); ++i) { + EXPECT(points_equal(ref[i], points[i])); + EXPECT(points_equal(ref[i], *it)); + ++it; + } + EXPECT(it == grid.end()); + + size_t i = 0; + for (const auto& it : grid) { + EXPECT(points_equal(ref[i++], it)); + } + EXPECT(i == grid.size()); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/grid_reorder.cc b/tests/geo/grid_reorder.cc new file mode 100644 index 000000000..ca395a03b --- /dev/null +++ b/tests/geo/grid_reorder.cc @@ -0,0 +1,81 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/Grid.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/testing/Test.h" + + +#define EXPECT_EQUAL_VECTOR(x, y) \ + { \ + EXPECT_EQUAL(x.size(), y.size()); \ + for (size_t i = 0; i < x.size(); ++i) { \ + EXPECT_EQUAL(x[i], y[i]); \ + } \ + } + + +namespace eckit::geo::test { + + +CASE("HEALPix") { + SECTION("HEALPix::reorder") { + std::unique_ptr spec(new spec::Custom({{"grid", "H2"}})); + std::unique_ptr ring(GridFactory::build(*spec)); + + static const Renumber expected_ren_ring_to_nested{ + 3, 7, 11, 15, 2, 1, 6, 5, 10, 9, 14, 13, 19, 0, 23, 4, 27, 8, 31, 12, 17, 22, 21, 26, + 25, 30, 29, 18, 16, 35, 20, 39, 24, 43, 28, 47, 34, 33, 38, 37, 42, 41, 46, 45, 32, 36, 40, 44, + }; + + const Renumber expected_ren_nested_to_ring{ + 13, 5, 4, 0, 15, 7, 6, 1, 17, 9, 8, 2, 19, 11, 10, 3, 28, 20, 27, 12, 30, 22, 21, 14, + 32, 24, 23, 16, 34, 26, 25, 18, 44, 37, 36, 29, 45, 39, 38, 31, 46, 41, 40, 33, 47, 43, 42, 35, + }; + + const Renumber expected_ren_none{ + 0, 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, + }; + + auto order_ring = ring->ordering(); + EXPECT_EQUAL(order_ring, Ordering::healpix_ring); + + auto ren_to_ring = ring->reorder(Ordering::healpix_ring); + EXPECT_EQUAL_VECTOR(ren_to_ring, expected_ren_none); + + auto ren_to_nested = ring->reorder(Ordering::healpix_nested); + EXPECT_EQUAL_VECTOR(ren_to_nested, expected_ren_ring_to_nested); + + + std::unique_ptr nested(ring->make_grid_reordered(Ordering::healpix_nested)); + + auto order_nested = nested->ordering(); + EXPECT_EQUAL(order_nested, Ordering::healpix_nested); + + ren_to_nested = nested->reorder(Ordering::healpix_nested); + EXPECT_EQUAL_VECTOR(ren_to_nested, expected_ren_none); + + ren_to_ring = nested->reorder(Ordering::healpix_ring); + EXPECT_EQUAL_VECTOR(ren_to_ring, expected_ren_nested_to_ring); + } +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/grid_to_points.cc b/tests/geo/grid_to_points.cc new file mode 100644 index 000000000..62600c912 --- /dev/null +++ b/tests/geo/grid_to_points.cc @@ -0,0 +1,93 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include + +#include "eckit/geo/grid/HEALPix.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("HEALPix") { + SECTION("HEALPix::to_points") { + std::unique_ptr grid(new grid::HEALPix(2, Ordering::healpix_ring)); + + static const std::vector expected_points_ring{ + {45, 66.443535691}, + {135, 66.443535691}, + {225, 66.443535691}, + {315, 66.443535691}, + {22.5, 41.810314896}, + {67.5, 41.810314896}, + {112.5, 41.810314896}, + {157.5, 41.810314896}, + {202.5, 41.810314896}, + {247.5, 41.810314896}, + {292.5, 41.810314896}, + {337.5, 41.810314896}, + {0, 19.471220634}, + {45, 19.471220634}, + {90, 19.471220634}, + {135, 19.471220634}, + {180, 19.471220634}, + {225, 19.471220634}, + {270, 19.471220634}, + {315, 19.471220634}, + {22.5, 0}, + {67.5, 0}, + {112.5, 0}, + {157.5, 0}, + {202.5, 0}, + {247.5, 0}, + {292.5, 0}, + {337.5, 0}, + {0, -19.471220634}, + {45, -19.471220634}, + {90, -19.471220634}, + {135, -19.471220634}, + {180, -19.471220634}, + {225, -19.471220634}, + {270, -19.471220634}, + {315, -19.471220634}, + {22.5, -41.810314896}, + {67.5, -41.810314896}, + {112.5, -41.810314896}, + {157.5, -41.810314896}, + {202.5, -41.810314896}, + {247.5, -41.810314896}, + {292.5, -41.810314896}, + {337.5, -41.810314896}, + {45, -66.443535691}, + {135, -66.443535691}, + {225, -66.443535691}, + {315, -66.443535691}, + }; + + auto points = grid->to_points(); + EXPECT_EQUAL(points.size(), expected_points_ring.size()); + + for (int i = 0; i < points.size(); ++i) { + EXPECT(points_equal(std::get(points[i]), expected_points_ring[i])); + } + } +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/increments.cc b/tests/geo/increments.cc new file mode 100644 index 000000000..e33e0c88c --- /dev/null +++ b/tests/geo/increments.cc @@ -0,0 +1,70 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Increments.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("checks") { + EXPECT_NO_THROW(Increments(1, 2)); + EXPECT_NO_THROW(Increments(1, -2)); + EXPECT_NO_THROW(Increments(-1, 2)); + EXPECT_NO_THROW(Increments(-1, -2)); + + EXPECT_THROWS(Increments(-1, 0)); + EXPECT_THROWS(Increments(0, -2)); + EXPECT_THROWS(Increments(0, 0)); + EXPECT_THROWS(Increments(0, 2)); + EXPECT_THROWS(Increments(1, 0)); +} + + +CASE("assignment") { + Increments a(10, 1); + + Increments b(20, 2); + EXPECT_NOT_EQUAL(a.dx, b.dx); + EXPECT_NOT_EQUAL(a, b); + + b = a; + EXPECT_EQUAL(a.dx, b.dx); + EXPECT_EQUAL(a, b); + + b = {30, b.dy}; + EXPECT_EQUAL(b.dx, 30); + EXPECT_EQUAL(a.dx, 10); + + Increments c(a); + EXPECT_EQUAL(a.dx, c.dx); + EXPECT_EQUAL(a, c); + + c = {40, c.dy}; + EXPECT_EQUAL(c.dx, 40); + EXPECT_EQUAL(a.dx, 10); + + auto d(std::move(a)); + EXPECT_EQUAL(d.dx, 10); + + d = {50, d.dy}; + EXPECT_EQUAL(d.dx, 50); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/iterator.cc b/tests/geo/iterator.cc new file mode 100644 index 000000000..f83ab760f --- /dev/null +++ b/tests/geo/iterator.cc @@ -0,0 +1,28 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Iterator.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("") {} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/kdtree.cc b/tests/geo/kdtree.cc new file mode 100644 index 000000000..f1a86930e --- /dev/null +++ b/tests/geo/kdtree.cc @@ -0,0 +1,302 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/container/KDTree.h" +#include "eckit/geo/Point2.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +//---------------------------------------------------------------------------------------------------------------------- + +struct TestTreeTrait { + using Point = geo::Point2; + using Payload = double; +}; + +//---------------------------------------------------------------------------------------------------------------------- + +/// \brief Class used to test whether any point in a kd-tree lies in the interior of an +/// axis-aligned box. +template +struct PointInBoxInteriorFinder { + using KDTree = KDTreeX; + using Point = typename KDTree::Point; + using Alloc = typename KDTree::Alloc; + using Node = typename KDTree::Node; + + /// \brief Returns true if any point in \p tree lies in the interior of the specified + /// axis-aligned box. + /// + /// \param tree + /// Tree to search. + /// \param lbound + /// Lower-left corner of the axis-aligned box. + /// \param ubound + /// Upper-right corner of the axis-aligned box. + static bool isAnyPointInBoxInterior(const KDTree& tree, const Point& lbound, const Point& ubound) { + if (!tree.root_) { + return false; + } + + auto& alloc = tree.alloc_; + auto* root = alloc.convert(tree.root_, static_cast(nullptr)); + ASSERT(root != nullptr); + + return isAnyPointInBoxInterior(root, alloc, lbound, ubound); + } + +private: + /// \brief Returns true if the point stored in \p node or any of its descendants lies in the + /// interior of the axis-aligned box with bottom-left and top-right corners at + /// \p lbound and \p ubound. + static bool isAnyPointInBoxInterior(const Node* node, Alloc& alloc, const Point& lbound, const Point& ubound) { + if (node == nullptr) { + return false; + } + + const auto& point = node->value().point(); + + if (isPointInBoxInterior(point, lbound, ubound)) { + return true; + } + + const size_t axis = node->axis(); + + return (lbound.x(axis) < point.x(axis) && isAnyPointInBoxInterior(node->left(alloc), alloc, lbound, ubound)) + || (ubound.x(axis) > point.x(axis) + && isAnyPointInBoxInterior(node->right(alloc), alloc, lbound, ubound)); + } + + /// \brief Returns true if \p point is in the interior of the axis-aligned box + /// with bottom-left and top-right corners at \p lbound and \p ubound. + static bool isPointInBoxInterior(const Point& point, const Point& lbound, const Point& ubound) { + for (size_t d = 0; d < Point::DIMS; ++d) { + if (point.x(d) <= lbound.x(d) || point.x(d) >= ubound.x(d)) { + return false; + } + } + return true; + } +}; + +//---------------------------------------------------------------------------------------------------------------------- + +/// \brief Returns true if any point in \p tree is in the interior of the axis-aligned box +/// with bottom-left and top-right corners at \p lbound and \p ubound. +template +bool isAnyPointInBoxInterior(const KDTreeX& tree, const typename KDTreeX::Point& lbound, + const typename KDTreeX::Point& ubound) { + return PointInBoxInteriorFinder::isAnyPointInBoxInterior(tree, lbound, ubound); +} + +//---------------------------------------------------------------------------------------------------------------------- + +#define EXPECT_POINT_EQUAL(a, b) \ + for (size_t i = 0; i < Point::dimensions(); ++i) { \ + EXPECT(a.x(i) == b.x(i)); \ + } + + +CASE("test_eckit_container_kdtree_constructor") { + using Tree = KDTreeMemory; + using Point = Tree::PointType; + + // build k-d tree (offline) + Tree kd; + std::vector points; + for (size_t i = 0; i < 10; ++i) { + for (size_t j = 0; j < 10; ++j) { + points.emplace_back(Point{static_cast(i), static_cast(j)}, 99.9); + } + } + kd.build(points.begin(), points.end()); + + // size + EXPECT_EQUAL(kd.size(), points.size()); + + // pick a point + auto ref = points[points.size() / 2].point(); + + SECTION("test single closest point") { + // a point similar to an existing one + EXPECT_POINT_EQUAL(ref, kd.nearestNeighbour(ref + Point{0.1, 0.1}).point()); + + // exact match to a point + EXPECT_POINT_EQUAL(ref, kd.nearestNeighbour(ref).point()); + + // off the scale, i.e. not within a group of points (+) + EXPECT_POINT_EQUAL(points.back().point(), + kd.nearestNeighbour(points.back().point() + Point{1000., 0.}).point()); + + // off the scale, i.e. not within a group of points (-) + EXPECT_POINT_EQUAL(points.front().point(), + kd.nearestNeighbour(points.front().point() + Point{-1000., 0.}).point()); + } + + SECTION("test N nearest") { + // move this point so it lies between four equally, make sure we differ by 0.5 along each axis + auto test = ref + Point{0.5, 0.5}; + + for (auto& near : kd.kNearestNeighbours(test, 4)) { + auto diff = near.point() - test; + for (size_t i = 0; i < Point::dimensions(); ++i) { + EXPECT(Point::distance(Point{0., 0.}, diff, i) == 0.5); + } + } + } + + SECTION("test a custom visitor") { + // Test a custom visitor. The purpose of doing that in this test is to ensure that the public + // interface of KDTree is sufficient to write a custom class traversing the tree. + auto a = Point{0.25, 0.25}; + auto lbound = ref - a; + auto ubound = ref + a; + EXPECT(isAnyPointInBoxInterior(kd, lbound, ubound)); + + auto b = Point{0.5, 0.5}; + lbound = lbound + b; + ubound = ubound + b; + EXPECT_NOT(isAnyPointInBoxInterior(kd, lbound, ubound)); + } +} + +CASE("test_eckit_container_kdtree_insert") { + using Tree = KDTreeMemory; + using Point = Tree::PointType; + + // build k-d tree (online) + Tree kd; + std::vector points; + for (size_t i = 0; i < 10; ++i) { + for (size_t j = 0; j < 10; ++j) { + points.emplace_back(Point{static_cast(i), static_cast(j)}, 99.9); + kd.insert(points.back()); + } + } + + // size + EXPECT_EQUAL(kd.size(), points.size()); + + // pick a point + auto ref = points[points.size() / 2].point(); + + SECTION("test single closest point") { + // a point similar to an existing one + EXPECT_POINT_EQUAL(ref, kd.nearestNeighbour(ref + Point{0.1, 0.1}).point()); + + // exact match to a point + EXPECT_POINT_EQUAL(ref, kd.nearestNeighbour(ref).point()); + + // off the scale, i.e. not within a group of points (+) + EXPECT_POINT_EQUAL(points.back().point(), + kd.nearestNeighbour(points.back().point() + Point{1000., 0.}).point()); + + // off the scale, i.e. not within a group of points (-) + EXPECT_POINT_EQUAL(points.front().point(), + kd.nearestNeighbour(points.front().point() + Point{-1000., 0.}).point()); + } + + SECTION("test N nearest") { + // move this point so it lies between four equally, make sure we differ by 0.5 along each axis + auto test = ref + Point{0.5, 0.5}; + + for (auto& near : kd.kNearestNeighbours(test, 4)) { + auto diff = near.point() - test; + for (size_t i = 0; i < Point::dimensions(); ++i) { + EXPECT(Point::distance(Point{0., 0.}, diff, i) == 0.5); + } + } + } +} + +CASE("test_kdtree_mapped") { + using Tree = KDTreeMapped; + using Point = Tree::PointType; + + std::vector points; + for (size_t i = 0; i < 10; ++i) { + for (size_t j = 0; j < 10; ++j) { + points.emplace_back(Point{static_cast(i), static_cast(j)}, 99.9); + } + } + + // pick a point + auto ref = points[points.size() / 2].point(); + + auto passTest = [&](Tree& kd, const Point& p) -> bool { + // perturb it a little + // we should find the same point + auto nr = kd.nearestNeighbour(p + Point{0.1, 0.1}).point(); + for (size_t i = 0; i < Point::dimensions(); ++i) { + if (nr.x(i) != p.x(i)) { + return false; + } + } + return true; + }; + + PathName path("test_kdtree_mapped.kdtree"); + + // Write file with k-d tree + { + if (path.exists()) { + path.unlink(); + } + + Tree kd(path, points.size(), 0); + EXPECT(kd.empty()); + + kd.build(points); + + EXPECT_EQUAL(kd.size(), points.size()); + EXPECT(passTest(kd, ref)); + } + + // Load file with k-d tree + { + Tree kd(path, 0, 0); + + // Cannot insert point as the tree is readonly + EXPECT_THROWS_AS(kd.insert(points.front()), AssertionFailed); + + // Cannot build with points as the tree is readonly + EXPECT_THROWS_AS(kd.build(points), AssertionFailed); + + EXPECT_EQUAL(kd.size(), points.size()); + EXPECT(passTest(kd, ref)); + } +} + +CASE("test_kdtree_iterate_empty") { + using Tree = KDTreeMemory; + + size_t count = 0; + Tree kd; + for (auto& item : kd) { + count++; + } + EXPECT_EQUAL(count, 0); + EXPECT(kd.empty()); +} + +//---------------------------------------------------------------------------------------------------------------------- + +} // namespace eckit::geo::test + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/ordering.cc b/tests/geo/ordering.cc new file mode 100644 index 000000000..ee4c9d4c4 --- /dev/null +++ b/tests/geo/ordering.cc @@ -0,0 +1,48 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/Ordering.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("ordering ('scan')") { + int ordering = 0; + + for (bool scan_alternating : {false, true}) { + for (bool scan_ij : {true, false}) { + for (bool j_plus : {false, true}) { + for (bool i_plus : {true, false}) { + spec::Custom spec({{"scan_i_plus", i_plus}, + {"scan_j_plus", j_plus}, + {"scan_ij", scan_ij}, + {"scan_alternating", scan_alternating}}); + + EXPECT(static_cast(make_ordering_from_spec(spec)) == ordering); + + ++ordering; + } + } + } + } +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/point.cc b/tests/geo/point.cc new file mode 100644 index 000000000..fb9c5a493 --- /dev/null +++ b/tests/geo/point.cc @@ -0,0 +1,53 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/Point.h" +#include "eckit/geo/Point2.h" +#include "eckit/geo/Point3.h" +#include "eckit/geo/PointLonLat.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("Point comparison") { + auto r(PointLonLat::make(-10., -91.)); + EXPECT(points_equal(r, r.antipode().antipode())); + + for (Point a1 : {PointLonLat{-180., 0.}, PointLonLat{180., 10.}, PointLonLat{0., 90.}, PointLonLat{1., -90.}}) { + auto a2 = PointLonLat::make(std::get(a1).lon + 720., std::get(a1).lat); + EXPECT(points_equal(a1, a2)); + } + + Point2 p2{1., 2.}; + Point3 p3{1., 2., 3.}; + PointLonLat pll{4., 5.}; + + for (const auto& [a, b] : {std::pair{p2, p2}, {p3, p3}, {pll, pll}}) { + EXPECT(points_equal(a, b)); + } + + for (const auto& [a, b] : {std::pair{p2, p3}, {p2, pll}, {p3, p2}, {p3, pll}, {pll, p2}, {pll, p3}}) { + EXPECT_THROWS_AS(points_equal(a, b), AssertionFailed); + } +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/point2.cc b/tests/geo/point2.cc new file mode 100644 index 000000000..95d379c3d --- /dev/null +++ b/tests/geo/point2.cc @@ -0,0 +1,124 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2. + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/Point2.h" +#include "eckit/testing/Test.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::test { + + +CASE("Point2 initialisation") { + Point2 z; + + EXPECT(z.X == 0.); + EXPECT(z.Y == 0.); + + Point2 q{4., 5.}; + + EXPECT(q.X == 4.); + EXPECT(q.Y == 5.); + + Point2 r(q); + + EXPECT(r.X == 4.); + EXPECT(r.Y == 5.); +} + + +CASE("Point2 addition") { + Point2 p1{1., 2.}; + Point2 p2{2., 4.}; + + Point2 r = p1 + p2; + + EXPECT(r.X == 3.); + EXPECT(r.Y == 6.); +} + + +CASE("Point2 subtraction") { + Point2 p1{2., 5.}; + Point2 p2{1., 2.}; + + Point2 r = p1 - p2; + + EXPECT(r.X == 1.); + EXPECT(r.Y == 3.); +} + + +CASE("Point2 scaling") { + Point2 p1{1., 2.}; + Point2 p2(p1); + + Point2 r = p1 * 42.; + + EXPECT(r.X == 42.); + EXPECT(r.Y == 84.); + + Point2 oo; + + Point2 p3 = p2 * 2.; + Point2 p4 = p3 + p2; + Point2 p5 = p4 - p2 * 3; + EXPECT(p5 == oo); +} + + +CASE("Point2 equality") { + Point2 p1{1., 2.}; + Point2 p2{1., 2.}; + + EXPECT(p1 == p2); +} + + +CASE("Point2 inequality") { + Point2 p1{1., 3.}; + Point2 p2{1., 4.}; + + EXPECT(p1 != p2); +} + + +CASE("Point2 distance comparison") { + Point2 p1{2., 1.}; + Point2 p2{1., 2.}; + Point2 p3{5., 5.}; + + EXPECT(types::is_approximately_equal(std::sqrt(2.), p1.distance(p2))); + EXPECT(types::is_approximately_equal(5., p1.distance(p3))); +} + + +CASE("Point2 distance2 comparison") { + Point2 p1{2., 1.}; + Point2 p2{1., 2.}; + Point2 p3{5., 5.}; + + EXPECT(types::is_approximately_equal(p1.distance2(p2), 2.)); + EXPECT(types::is_approximately_equal(p1.distance2(p3), 25.)); + + EXPECT(p2.distance2(p1) < p3.distance2(p1)); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/point3.cc b/tests/geo/point3.cc new file mode 100644 index 000000000..156369668 --- /dev/null +++ b/tests/geo/point3.cc @@ -0,0 +1,117 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/Point3.h" +#include "eckit/testing/Test.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::test { + + +CASE("Point3 initialisation") { + Point3 z; + + EXPECT(z.X == 0.); + EXPECT(z.Y == 0.); + EXPECT(z.Z == 0.); + + Point3 p = {1., 2., 3.}; + Point3 s(p); + + EXPECT(s.X == 1.); + EXPECT(s.Y == 2.); + EXPECT(s.Z == 3.); +} + + +CASE("Point3 addition") { + Point3 p1 = {1., 2., 3.}; + Point3 p2{1., 2., 3.}; + + Point3 r = p1 + p2; + + EXPECT(r.X == 2.); + EXPECT(r.Y == 4.); + EXPECT(r.Z == 6.); +} + + +CASE("Point3 subtraction") { + Point3 p1{2., 5., 7.}; + Point3 p2{1., 2., 3.}; + + Point3 r = p1 - p2; + + EXPECT(r.X == 1.); + EXPECT(r.Y == 3.); + EXPECT(r.Z == 4.); +} + + +CASE("Point3 scaling") { + Point3 p1{1., 2., 3.}; + + Point3 r = p1 * 42.; + + EXPECT(r.X == 42.); + EXPECT(r.Y == 84.); + EXPECT(r.Z == 126.); +} + + +CASE("Point3 equality") { + Point3 p1{1., 2., 3.}; + Point3 p2{1., 2., 3.}; + + EXPECT(p1 == p2); +} + + +CASE("Point3 inequality") { + Point3 p1{1., 2., 3.}; + Point3 p2{1., 2., 4.}; + + EXPECT(p1 != p2); +} + + +CASE("Point3 distance comparison") { + Point3 p1{2., 1., 0.}; + Point3 p2{1., 2., 4.}; + Point3 p3{5., 5., 5.}; + + EXPECT(types::is_approximately_equal(std::sqrt(18.), p1.distance(p2))); + EXPECT(types::is_approximately_equal(std::sqrt(50.), p1.distance(p3))); +} + + +CASE("Point3 distance2 comparison") { + Point3 p1{2., 1., 0.}; + Point3 p2{1., 2., 4.}; + Point3 p3; + + EXPECT(types::is_approximately_equal(p1.distance2(p2), 18.)); + EXPECT(types::is_approximately_equal(p1.distance2(p3), 5.)); + + EXPECT(p2.distance2(p1) > p3.distance2(p1)); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/pointlonlat.cc b/tests/geo/pointlonlat.cc new file mode 100644 index 000000000..5088dbef4 --- /dev/null +++ b/tests/geo/pointlonlat.cc @@ -0,0 +1,307 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/PointLonLat.h" +#include "eckit/geo/Point.h" +#include "eckit/geo/eckit_geo_config.h" +#include "eckit/testing/Test.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::test { + + +CASE("PointLonLat normalise_angle_to_*") { + struct test_t { + double angle; + double lim; + double ref; + }; + + + SECTION("normalise_angle_to_minimum") { + for (const test_t& test : { + test_t{10., 0., 10.}, + {0., 0., 0.}, + {-10., 0., 350.}, + {720., 0., 0.}, + {100., 90., 100.}, + {-370., 0., 350.}, + {100000., 0., static_cast(100000 % 360)}, + {-100., -180., -100.}, + {360., 0., 0.}, + {100000., 99960., 100000.}, + }) { + EXPECT(types::is_approximately_equal( + test.ref, PointLonLat::normalise_angle_to_minimum(test.angle, test.lim), PointLonLat::EPS)); + } + } + + + SECTION("normalise_angle_to_maximum") { + for (const auto& test : { + test_t{350., 360., 350.}, + {360., 360., 360.}, + {361., 360., 1.}, + {-720., 360., 360.}, + {100., 180., 100.}, + {-370., 360., 350.}, + {100000., 360., static_cast(100000 % 360)}, + {-100., -90., -100.}, + {720., 360., 360.}, + {100040., 100080., 100040.}, + }) { + EXPECT(types::is_approximately_equal( + test.ref, PointLonLat::normalise_angle_to_maximum(test.angle, test.lim), PointLonLat::EPS)); + } + } +} + + +CASE("PointLonLat antipode") { + PointLonLat p(300., -10.); + auto q = p.antipode(); + + EXPECT(points_equal(q, {120., 10.})); + EXPECT(points_equal(p, q.antipode())); + + PointLonLat r(-10., -91.); + + EXPECT(points_equal(r.antipode(), {350., 89.})); + EXPECT(points_equal(r, r.antipode().antipode())); + + PointLonLat s(1., -90.); + auto t = s.antipode(); + + EXPECT_EQUAL(t.lon, 0.); + EXPECT(points_equal(t, {2., 90.})); + EXPECT(points_equal(t.antipode(), s)); +} + + +#if eckit_HAVE_GEO_BITREPRODUCIBLE +CASE("bit-identical behaviour normalising angles") { + auto normalise = [](double a, double minimum) -> double { + auto modulo_360 = [](double a) { return a - 360. * std::floor(a / 360.); }; + auto diff = a - minimum; + return 0. <= diff && diff < 360. ? a : modulo_360(diff) + minimum; + }; + + struct test_t { + double angle; + double ref_norm_angle_m_360; + double ref_norm_angle_m_720; + double ref_norm_angle_p_360; + double ref_norm_angle_p_720; + }; + + for (const auto& test : { + test_t{0x1.a99999999999fp+3, 0x1.a9999999999ap+3, 0x1.a99999999998p+3, 0x1.a99999999998p+3, + 0x1.a99999999998p+3}, // 13.30000000000001 + {0x1.7599999999999p+5, 0x1.7599999999998p+5, 0x1.75999999999ap+5, 0x1.75999999999ap+5, + 0x1.75999999999ap+5}, // 46.699999999999996 + {-0x1.37823af2187f7p+4, -0x1.37823af2187fp+4, -0x1.37823af2188p+4, -0x1.37823af2188p+4, + -0x1.37823af2188p+4}, //-19.469294496237094 + {0x1.14f26c8adc252p+3, 0x1.14f26c8adc26p+3, 0x1.14f26c8adc24p+3, 0x1.14f26c8adc28p+3, + 0x1.14f26c8adc24p+3}, // 8.6545927726848824 + {0x1.237e9f537dd2dp+5, 0x1.237e9f537dd3p+5, 0x1.237e9f537dd3p+5, 0x1.237e9f537dd3p+5, + 0x1.237e9f537dd3p+5}, // 36.436827327992752 + {0x1.eb74b977e1e89p+5, 0x1.eb74b977e1e88p+5, 0x1.eb74b977e1e9p+5, 0x1.eb74b977e1e8p+5, + 0x1.eb74b977e1e9p+5}, // 61.431994377690962 + {0x1.1008717af4f67p+6, 0x1.1008717af4f68p+6, 0x1.1008717af4f68p+6, 0x1.1008717af4f68p+6, + 0x1.1008717af4f68p+6}, // 68.008245392991384 + {-0x1.b4f88656270d9p+4, -0x1.b4f88656270ep+4, -0x1.b4f88656270ep+4, -0x1.b4f88656270ep+4, + -0x1.b4f88656270ep+4}, //-27.31067498830166 + {-0x1.eb22f87f6ac12p+1, -0x1.eb22f87f6acp+1, -0x1.eb22f87f6acp+1, -0x1.eb22f87f6acp+1, + -0x1.eb22f87f6acp+1}, //-3.8370047208932272 + {0x1.40de11e0c3e99p+4, 0x1.40de11e0c3eap+4, 0x1.40de11e0c3eap+4, 0x1.40de11e0c3eap+4, + 0x1.40de11e0c3eap+4}, // 20.054216268529306 + {0x1.4aeba99be1331p+5, 0x1.4aeba99be133p+5, 0x1.4aeba99be133p+5, 0x1.4aeba99be133p+5, + 0x1.4aeba99be133p+5}, // 41.365069597063105 + {0x1.aa5c50f727ae6p+5, 0x1.aa5c50f727ae8p+5, 0x1.aa5c50f727aep+5, 0x1.aa5c50f727aep+5, + 0x1.aa5c50f727aep+5}, // 53.295076304338906 + {-0x1.556ccf04ef1bbp+4, -0x1.556ccf04ef1cp+4, -0x1.556ccf04ef1cp+4, -0x1.556ccf04ef1cp+4, + -0x1.556ccf04ef1cp+4}, //-21.339064616464139 + {0x1.556ccf04ef1bbp+4, 0x1.556ccf04ef1cp+4, 0x1.556ccf04ef1cp+4, 0x1.556ccf04ef1cp+4, + 0x1.556ccf04ef1cp+4}, // 21.339064616464139 + {0x1.388f683df92bbp+5, 0x1.388f683df92b8p+5, 0x1.388f683df92cp+5, 0x1.388f683df92cp+5, + 0x1.388f683df92cp+5}, // 39.070023044745049 + {-0x1.40de11e0c3e9dp+4, -0x1.40de11e0c3eap+4, -0x1.40de11e0c3eap+4, -0x1.40de11e0c3eap+4, + -0x1.40de11e0c3eap+4}, //-20.05421626852932 + {0x1.eb22f87f6abf5p+1, 0x1.eb22f87f6acp+1, 0x1.eb22f87f6acp+1, 0x1.eb22f87f6acp+1, + 0x1.eb22f87f6acp+1}, // 3.8370047208932143 + {0x1.b4f88656270d7p+4, 0x1.b4f88656270dp+4, 0x1.b4f88656270ep+4, 0x1.b4f88656270cp+4, + 0x1.b4f88656270ep+4}, // 27.310674988301653 + {-0x1.3f0f4411db559p+5, -0x1.3f0f4411db558p+5, -0x1.3f0f4411db56p+5, -0x1.3f0f4411db558p+5, + -0x1.3f0f4411db56p+5}, //-39.882454051500368 + {-0x1.63664f7d2181dp+5, -0x1.63664f7d2182p+5, -0x1.63664f7d2182p+5, -0x1.63664f7d2182p+5, + -0x1.63664f7d2182p+5}, //-44.424956300339751 + {-0x1.75e470fd085aap+5, -0x1.75e470fd085a8p+5, -0x1.75e470fd085bp+5, -0x1.75e470fd085a8p+5, + -0x1.75e470fd085bp+5}, //-46.7365436332869 + {-0x1.b2a6314996231p+4, -0x1.b2a631499623p+4, -0x1.b2a631499624p+4, -0x1.b2a631499624p+4, + -0x1.b2a631499624p+4}, //-27.165574347922476 + {-0x1.f720e2a9525edp+5, -0x1.f720e2a9525fp+5, -0x1.f720e2a9525fp+5, -0x1.f720e2a9525fp+5, + -0x1.f720e2a9525fp+5}, //-62.89105732233643 + {-0x1.236723c039272p+5, -0x1.236723c03927p+5, -0x1.236723c03927p+5, -0x1.236723c03927p+5, + -0x1.236723c03927p+5}, //-36.425361158126989 + {-0x1.7f9f1a40a5d1fp+4, -0x1.7f9f1a40a5d2p+4, -0x1.7f9f1a40a5d2p+4, -0x1.7f9f1a40a5d2p+4, + -0x1.7f9f1a40a5d2p+4}, //-23.976343395738805 + {0x1.ffffffffffffep+0, 0x1p+1, 0x1p+1, 0x1p+1, 0x1p+1}, // 1.9999999999999996 + {0x1.0b907154a92f7p+6, 0x1.0b907154a92f8p+6, 0x1.0b907154a92f8p+6, 0x1.0b907154a92f8p+6, + 0x1.0b907154a92f8p+6}, // 66.891057322336437 + {0x1.436723c039272p+5, 0x1.436723c03927p+5, 0x1.436723c03927p+5, 0x1.436723c03927p+5, + 0x1.436723c03927p+5}, // 40.425361158126989 + {0x1.bf9f1a40a5d1fp+4, 0x1.bf9f1a40a5d2p+4, 0x1.bf9f1a40a5d2p+4, 0x1.bf9f1a40a5d2p+4, + 0x1.bf9f1a40a5d2p+4}, // 27.976343395738805 + {0x1.0f266c20b79f9p+7, 0x1.0f266c20b79f8p+7, 0x1.0f266c20b79f8p+7, 0x1.0f266c20b79f8p+7, + 0x1.0f266c20b79f8p+7}, // 135.57504369966026 + {0x1.787bbbb54c676p+6, 0x1.787bbbb54c678p+6, 0x1.787bbbb54c678p+6, 0x1.787bbbb54c678p+6, + 0x1.787bbbb54c678p+6}, // 94.120833237446135 + {0x1.95e470fd085aap+5, 0x1.95e470fd085a8p+5, 0x1.95e470fd085bp+5, 0x1.95e470fd085ap+5, + 0x1.95e470fd085bp+5}, // 50.7365436332869 + {0x1.1bd0dd7b42b69p+7, 0x1.1bd0dd7b42b68p+7, 0x1.1bd0dd7b42b68p+7, 0x1.1bd0dd7b42b68p+7, + 0x1.1bd0dd7b42b68p+7}, // 141.90793976964349 + {0x1.19981bd70b549p+6, 0x1.19981bd70b548p+6, 0x1.19981bd70b548p+6, 0x1.19981bd70b548p+6, + 0x1.19981bd70b548p+6}, // 70.39854370123534 + {0x1.50bc8a12f525bp+5, 0x1.50bc8a12f5258p+5, 0x1.50bc8a12f526p+5, 0x1.50bc8a12f526p+5, + 0x1.50bc8a12f526p+5}, // 42.092060230356502 + {0x1.cb2a2664f7bbdp+6, 0x1.cb2a2664f7bbcp+6, 0x1.cb2a2664f7bcp+6, 0x1.cb2a2664f7bcp+6, + 0x1.cb2a2664f7bcp+6}, // 114.79116208803221 + {0x1.6784444ab398ap+6, 0x1.6784444ab3988p+6, 0x1.6784444ab3988p+6, 0x1.6784444ab3988p+6, + 0x1.6784444ab3988p+6}, // 89.879166762553865 + {0x1.83664f7d2181ep+5, 0x1.83664f7d2182p+5, 0x1.83664f7d2182p+5, 0x1.83664f7d2182p+5, + 0x1.83664f7d2182p+5}, // 48.424956300339758 + {0x1.380c1cb7eb45dp+7, 0x1.380c1cb7eb45cp+7, 0x1.380c1cb7eb45cp+7, 0x1.380c1cb7eb45cp+7, + 0x1.380c1cb7eb46p+7}, // 156.02365660426122 + {0x1.d46f8eab56d0bp+6, 0x1.d46f8eab56d0cp+6, 0x1.d46f8eab56d08p+6, 0x1.d46f8eab56d1p+6, + 0x1.d46f8eab56d08p+6}, // 117.10894267766359 + {0x1.5f0f4411db559p+5, 0x1.5f0f4411db558p+5, 0x1.5f0f4411db56p+5, 0x1.5f0f4411db56p+5, + 0x1.5f0f4411db56p+5}, // 43.882454051500368 + }) { + EXPECT(test.angle == normalise(test.angle, -180.)); + EXPECT(test.ref_norm_angle_m_360 == normalise(test.angle - 360., -180.)); + EXPECT(test.ref_norm_angle_m_720 == normalise(test.angle - 720., -180.)); + EXPECT(test.ref_norm_angle_p_360 == normalise(test.angle + 360., -180.)); + EXPECT(test.ref_norm_angle_p_720 == normalise(test.angle + 720., -180.)); + } +} +#endif + + +CASE("PointLonLat normalisation") { + constexpr auto da = 1e-3; + + for (double a : {-370., -190., -100., -90., -80., -10., 0., 10., 80., 90., 100., 190., 370.}) { + EXPECT(types::is_approximately_equal(a, PointLonLat::normalise_angle_to_minimum(a, a), PointLonLat::EPS)); + EXPECT(types::is_approximately_equal(a, PointLonLat::normalise_angle_to_maximum(a, a), PointLonLat::EPS)); + + EXPECT(types::is_approximately_equal(a + 360., PointLonLat::normalise_angle_to_minimum(a - da, a) + da, + PointLonLat::EPS)); + EXPECT(types::is_approximately_equal(a - 360., PointLonLat::normalise_angle_to_maximum(a + da, a) - da, + PointLonLat::EPS)); + } + + PointLonLat p(1, 90.); + EXPECT_EQUAL(p.lon, 1.); + EXPECT_EQUAL(p.lat, 90.); + + auto p2 = PointLonLat::make(p.lon, p.lat); + EXPECT_EQUAL(p2.lon, 0.); + EXPECT(points_equal(p, p2)); + + auto p3 = PointLonLat(50., 90.); + EXPECT(points_equal(p, p3)); +} + + +CASE("PointLonLat comparison") { + Point a1 = PointLonLat{180., 0.}; + Point a2 = PointLonLat{-180., 0.}; + EXPECT(points_equal(a1, a2)); + + Point b1 = PointLonLat{0., -90.}; + Point b2 = PointLonLat::make(1., 270.); + EXPECT(points_equal(b1, b2)); + + Point c1 = PointLonLat{300, -30}; + Point c2 = PointLonLat{-59.99999999999996, -30.000000000000018}; + EXPECT(points_equal(c1, c2)); + + Point d1 = PointLonLat{-178., -46.7}; + Point d2 = PointLonLat{-178.00000000000003, -46.7}; + EXPECT(points_equal(d1, d2)); + + Point e1 = PointLonLat(0., 90.); + Point e2 = PointLonLat(180., 90.); + EXPECT(points_equal(e1, e2)); + + Point f1 = PointLonLat(-0., -90.); + Point f2 = PointLonLat(-180., -90.); + EXPECT(points_equal(f1, f2)); +} + + +CASE("PointLonLat normalise angles") { + EXPECT(0. == PointLonLat::normalise_angle_to_minimum(360., 0.)); + EXPECT(14. == PointLonLat::normalise_angle_to_minimum(374., 0.)); + EXPECT(14. == PointLonLat::normalise_angle_to_minimum(374., -90.)); + EXPECT(374. == PointLonLat::normalise_angle_to_minimum(374., 90.)); + + EXPECT(14. == PointLonLat::normalise_angle_to_minimum(-346., 0.)); + EXPECT(14. == PointLonLat::normalise_angle_to_minimum(-346., -90.)); + EXPECT(374. == PointLonLat::normalise_angle_to_minimum(-346., 90.)); + EXPECT(0. == PointLonLat::normalise_angle_to_minimum(360. * 1e12, 0.)); + EXPECT(14. == PointLonLat::normalise_angle_to_minimum(360. * 1e12 + 14, 0.)); + EXPECT(0. == PointLonLat::normalise_angle_to_minimum(-360. * 1e12, 0.)); + EXPECT(14. == PointLonLat::normalise_angle_to_minimum(-360. * 1e12 + 14, 0.)); +} + + +CASE("PointLonLat canonicalise on sphere") { + const PointLonLat p1(108., 32.); + + // Worse coordinates for the same point: + const auto p2 = PointLonLat::make(-252., 32.); + const auto p3 = PointLonLat::make(288., 148.); + const auto p4 = PointLonLat::make(108., -328.); + + // Check each of these is correctly shifted back to original point: + const PointLonLat q2 = PointLonLat::make(p2.lon, p2.lat); + const PointLonLat q3 = PointLonLat::make(p3.lon, p3.lat); + const PointLonLat q4 = PointLonLat::make(p4.lon, p4.lat); + + EXPECT(p1.lon == q2.lon); + EXPECT(p1.lat == q2.lat); + EXPECT(p1.lon == q3.lon); + EXPECT(p1.lat == q3.lat); + EXPECT(p1.lon == q4.lon); + EXPECT(p1.lat == q4.lat); + + // Check with longitude offset + EXPECT(points_equal(PointLonLat::make(1., -90.), PointLonLat(0., -90.))); + EXPECT(points_equal(PointLonLat::make(2., 90.), PointLonLat(0., 90.))); + EXPECT(points_equal(PointLonLat::make(3., 180.), PointLonLat(183., 0.))); + + // Check with latitude offset + constexpr double eps = 1.e-6; + + EXPECT(points_equal(PointLonLat::make(-1., 89.99999914622634, 0., eps), {0., 90.})); + EXPECT(points_equal(PointLonLat::make(1., -89.99999914622634, 0., eps), {0., -90.})); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/pointlonlatr.cc b/tests/geo/pointlonlatr.cc new file mode 100644 index 000000000..61742fe2f --- /dev/null +++ b/tests/geo/pointlonlatr.cc @@ -0,0 +1,131 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/geo/PointLonLatR.h" +#include "eckit/geo/PointLonLat.h" +#include "eckit/geo/util.h" +#include "eckit/testing/Test.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::test { + + +CASE("PointLonLatR normalise_angle_to_*") { + struct test_t { + double angle; + double lim; + double ref; + }; + + + SECTION("normalise_angle_to_minimum") { + for (const auto& test : { + test_t{1., 0., 1.}, + {1. + 42. * PointLonLatR::FULL_ANGLE, 0., 1.}, + {1. - 42. * PointLonLatR::FULL_ANGLE, 0., 1.}, + {1., 3. * PointLonLatR::FULL_ANGLE, 3. * PointLonLatR::FULL_ANGLE + 1.}, + {-1., 3. * PointLonLatR::FULL_ANGLE, 4. * PointLonLatR::FULL_ANGLE - 1.}, + }) { + EXPECT(types::is_approximately_equal( + test.ref, PointLonLatR::normalise_angle_to_minimum(test.angle, test.lim), PointLonLatR::EPS)); + } + } + + + SECTION("normalise_angle_to_maximum") { + for (const auto& test : { + test_t{1., 0., 1. - PointLonLatR::FULL_ANGLE}, + {1., 3. * PointLonLatR::FULL_ANGLE, 2. * PointLonLatR::FULL_ANGLE + 1.}, + {-1., 3. * PointLonLatR::FULL_ANGLE, 3. * PointLonLatR::FULL_ANGLE - 1.}, + }) { + EXPECT(types::is_approximately_equal( + test.ref, PointLonLatR::normalise_angle_to_maximum(test.angle, test.lim), PointLonLatR::EPS)); + } + } +} + + +CASE("PointLonLatR normalisation") { + PointLonLatR p(1, PointLonLatR::RIGHT_ANGLE); + auto p2 = PointLonLatR::make(p.lonr, p.latr); + auto p3 = PointLonLatR(1. + 42. * PointLonLatR::FULL_ANGLE, PointLonLatR::RIGHT_ANGLE); + + EXPECT_EQUAL(p2.lonr, 0.); + EXPECT(points_equal(p, p2)); + EXPECT(points_equal(p, p3)); + + PointLonLatR q(1., SOUTH_POLE_R.latr); + auto q2 = q.antipode(); + auto q3 = q2.antipode(); + + EXPECT_EQUAL(q2.lonr, 0.); + EXPECT(points_equal(q2, p)); + EXPECT(points_equal(q3, q)); +} + + +CASE("PointLonLatR comparison") { + auto r(PointLonLatR::make(-10., -91.)); + EXPECT(points_equal(r, r.antipode().antipode())); + + PointLonLatR a1{PointLonLatR::FLAT_ANGLE, 0.}; + PointLonLatR a2{-PointLonLatR::FLAT_ANGLE, 0.}; + EXPECT(points_equal(a1, a2)); + + EXPECT(points_equal(SOUTH_POLE_R, {1., SOUTH_POLE_R.latr + PointLonLatR::FULL_ANGLE})); + + PointLonLatR c1{300., -30.}; + PointLonLatR c2{c1.lonr - PointLonLatR::FULL_ANGLE - PointLonLatR::EPS / 10., + c1.latr + PointLonLatR::FULL_ANGLE + PointLonLatR::EPS / 10.}; + EXPECT(points_equal(c1, c2)); + + EXPECT(points_equal(NORTH_POLE_R, {-42, PointLonLatR::RIGHT_ANGLE})); + EXPECT(points_equal(SOUTH_POLE_R, {42., -PointLonLatR::RIGHT_ANGLE})); +} + + +CASE("PointLonLatR normalise angles") { + EXPECT(types::is_approximately_equal( + 0., PointLonLatR::normalise_angle_to_minimum(0. + PointLonLatR::FULL_ANGLE, 0.), PointLonLatR::EPS)); + + EXPECT(types::is_approximately_equal( + 1., PointLonLatR::normalise_angle_to_minimum(1. + PointLonLatR::FULL_ANGLE * 11, 0.), PointLonLatR::EPS)); + + EXPECT(types::is_approximately_equal( + 2. + PointLonLatR::FULL_ANGLE * 11, + PointLonLatR::normalise_angle_to_minimum(2. + PointLonLatR::FULL_ANGLE * 11, PointLonLatR::FULL_ANGLE * 11), + PointLonLatR::EPS)); +} + + +CASE("PointLonLatR conversion to/from PointLonLat") { + PointLonLatR p{0., 0.}; + EXPECT(points_equal(p, PointLonLatR::make_from_lonlat(0., 0.))); + + PointLonLatR q{0., PointLonLatR::RIGHT_ANGLE}; + EXPECT(points_equal(q, PointLonLatR::make_from_lonlat(1., PointLonLat::RIGHT_ANGLE))); + + PointLonLatR r{42. * PointLonLatR::FULL_ANGLE, SOUTH_POLE_R.latr}; + EXPECT(points_equal(r, PointLonLatR::make_from_lonlat(0., SOUTH_POLE.lat - 42. * PointLonLat::FULL_ANGLE))); + + PointLonLatR s{10. * util::DEGREE_TO_RADIAN, 42. * PointLonLatR::FULL_ANGLE}; + EXPECT(points_equal(s, PointLonLatR::make_from_lonlat(10. - 42. * PointLonLat::FULL_ANGLE, 0.))); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/projection.cc b/tests/geo/projection.cc new file mode 100644 index 000000000..47add37be --- /dev/null +++ b/tests/geo/projection.cc @@ -0,0 +1,59 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/Projection.h" +#include "eckit/geo/figure/UnitSphere.h" +#include "eckit/geo/projection/LonLatToXYZ.h" // to test Reverse +#include "eckit/geo/projection/Reverse.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("projection: none") { + Point p = PointLonLat{1, 1}; + std::unique_ptr projection(ProjectionFactory::instance().get("none").create(spec::Custom{})); + + EXPECT(points_equal(p, projection->inv(p))); + EXPECT(points_equal(p, projection->fwd(p))); +} + + +CASE("projection: reverse") { + projection::LonLatToXYZ ab(new figure::UnitSphere); + projection::Reverse ba(new figure::UnitSphere); + + PointLonLat p = NORTH_POLE; + Point3 q{0., 0., 1.}; + + ASSERT(points_equal(q, ab.fwd(p))); + ASSERT(points_equal(p, ab.inv(q))); + + // ensure fwd(Point3) -> PointLonLat, inv(PointLonLat) -> Point3 + EXPECT(points_equal(p, ba.fwd(q))); + EXPECT(points_equal(q, ba.inv(p))); + + ASSERT(std::unique_ptr(ab.spec())->get_string("projection") == "ll_to_xyz"); + EXPECT(std::unique_ptr(ba.spec())->get_string("projection") == "reverse_ll_to_xyz"); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/projection_ll_to_xyz.cc b/tests/geo/projection_ll_to_xyz.cc new file mode 100644 index 000000000..6d40cf28b --- /dev/null +++ b/tests/geo/projection_ll_to_xyz.cc @@ -0,0 +1,139 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include + +#include "eckit/geo/projection/LonLatToXYZ.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("projection: ll_to_xyz") { + struct P : std::unique_ptr { + explicit P(Projection* ptr) : unique_ptr(ptr) { ASSERT(unique_ptr::operator bool()); } + }; + + struct test_t { + PointLonLat a; + Point3 b; + }; + + constexpr double R = 1.; + const auto L = R * std::sqrt(2.) / 2.; + + + // spherical projections + P to_xyz_1(ProjectionFactory::instance().get("ll_to_xyz").create(spec::Custom{{"R", 1.}})); + P to_xyz_2(new projection::LonLatToXYZ(1., 1.)); + + EXPECT(*to_xyz_1 == *to_xyz_2); + EXPECT(*to_xyz_1 == projection::LonLatToXYZ(1.)); + + + // oblate spheroid projections + P to_xyz_3(ProjectionFactory::instance().get("ll_to_xyz").create(spec::Custom{{"a", 1.}, {"b", 0.5}})); + P to_xyz_4(new projection::LonLatToXYZ(1., 0.5)); + + EXPECT(*to_xyz_3 == *to_xyz_4); + + + // problate spheroid (not supported) + EXPECT_THROWS(projection::LonLatToXYZ(0.5, 1.)); + + + SECTION("spec") { + Log::info() << to_xyz_1->spec_str() << std::endl; + Log::info() << to_xyz_2->spec_str() << std::endl; + Log::info() << to_xyz_3->spec_str() << std::endl; + Log::info() << to_xyz_4->spec_str() << std::endl; + EXPECT(to_xyz_1->spec_str() == R"({"projection":"ll_to_xyz","r":1})"); + EXPECT(to_xyz_2->spec_str() == R"({"projection":"ll_to_xyz","r":1})"); + EXPECT(to_xyz_3->spec_str() == R"({"a":1,"b":0.5,"projection":"ll_to_xyz"})"); + EXPECT(to_xyz_4->spec_str() == R"({"a":1,"b":0.5,"projection":"ll_to_xyz"})"); + } + + + SECTION("roundtrip") { + for (const auto& p : { + PointLonLat{1, 1}, + PointLonLat{1, 0}, + PointLonLat(723., 1.), + }) { + EXPECT(points_equal(p, to_xyz_1->inv(to_xyz_1->fwd(p)))); + EXPECT(points_equal(p, to_xyz_2->inv(to_xyz_2->fwd(p)))); + + EXPECT(points_equal(to_xyz_1->fwd(p), to_xyz_2->fwd(p))); + EXPECT(points_equal(to_xyz_2->fwd(p), to_xyz_1->fwd(p))); + + if (p.lat == 0) { + auto q = to_xyz_3->fwd(p); + EXPECT(points_equal(to_xyz_1->fwd(p), q)); + EXPECT(points_equal(to_xyz_2->inv(q), p)); + } + } + } + + + SECTION("sphere (ll -> xyz, xyz -> ll)") { + for (const auto& test : { + test_t{{0, 90}, {0, 0, R}}, // + {{0, -90}, {0, 0, -R}}, // + {{0, 0}, {R, 0, 0}}, // + {{-360, 0}, {R, 0, 0}}, // + {{90, 0}, {0, R, 0}}, // + {{-270, 0}, {0, R, 0}}, // + {{180, 0}, {-R, 0, 0}}, // + {{-180, 0}, {-R, 0, 0}}, // + {{270, 0}, {0, -R, 0}}, // + {{-90, 0}, {0, -R, 0}}, // + {{45, 0}, {L, L, 0}}, // + {{-315, 0}, {L, L, 0}}, // + {{135, 0}, {-L, L, 0}}, // + {{-225, 0}, {-L, L, 0}}, // + {{225, 0}, {-L, -L, 0}}, // + {{-135, 0}, {-L, -L, 0}}, // + {{315, 0}, {L, -L, 0}}, // + {{-45, 0}, {L, -L, 0}}, // + }) { + EXPECT(points_equal(to_xyz_1->fwd(test.a), test.b)); + EXPECT(points_equal(to_xyz_1->inv(test.b), test.a)); + + EXPECT(points_equal(to_xyz_2->fwd(test.a), test.b)); + EXPECT(points_equal(to_xyz_2->inv(test.b), test.a)); + } + } + + + SECTION("spheroid (ll -> xyz)") { + for (const auto& test : { + test_t{{0, -90}, {0, 0, -0.5}}, // + {{42, -90}, {0, 0, -0.5}}, // + {{0, 90}, {0, 0, 0.5}}, // + {{42, 90}, {0, 0, 0.5}}, // + }) { + EXPECT(points_equal(to_xyz_3->fwd(test.a), test.b)); + EXPECT(points_equal(to_xyz_4->fwd(test.a), test.b)); + } + } +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/projection_mercator.cc b/tests/geo/projection_mercator.cc new file mode 100644 index 000000000..e3a9ad347 --- /dev/null +++ b/tests/geo/projection_mercator.cc @@ -0,0 +1,72 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/eckit_geo_config.h" +#include "eckit/geo/projection/Mercator.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("Mercator: spec_str, proj_str") { + projection::Mercator proj1({0., 14.}, {262.036, 14.7365}); + projection::Mercator proj2({0., 14.}, {0., 0.}); + projection::Mercator proj3({-180., 0.}, {0., 0.}); + + + SECTION("inv(fwd(.)) == . and fwd(inv(.)) == .") { + for (const auto& projection : { + proj1, + proj2, + proj3, + }) { + Point2 a{0., 0.}; + EXPECT(points_equal(a, projection.fwd(projection.inv(a)))); + + PointLonLat b{-75., 35.}; + EXPECT(points_equal(b, projection.inv(projection.fwd(b)))); + + Point2 c = projection.fwd(NORTH_POLE); + EXPECT(c.Y > std::numeric_limits::max()); + + Point2 d = projection.fwd(SOUTH_POLE); + EXPECT(d.Y < std::numeric_limits::lowest()); + } + } + + + SECTION("spec_str") { + EXPECT(proj1.spec_str() == R"({"lat_ts":14,"projection":"mercator","r":6371229})"); + EXPECT(proj2.spec_str() == R"({"lat_ts":14,"projection":"mercator","r":6371229})"); + EXPECT(proj3.spec_str() == R"({"lon_0":-180,"projection":"mercator","r":6371229})"); + } + + +#if eckit_HAVE_PROJ + SECTION("proj_str") { + EXPECT(proj1.proj_str() == "+proj=merc +lat_ts=14 +R=6371229"); + EXPECT(proj2.proj_str() == "+proj=merc +lat_ts=14 +R=6371229"); + EXPECT(proj3.proj_str() == "+proj=merc +lon_0=-180 +R=6371229"); + } +#endif +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/projection_plate-caree.cc b/tests/geo/projection_plate-caree.cc new file mode 100644 index 000000000..1791750ad --- /dev/null +++ b/tests/geo/projection_plate-caree.cc @@ -0,0 +1,44 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/Projection.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +using P = std::unique_ptr; + + +CASE("projection: plate-caree") { + Point p = PointLonLat{1, 1}; + Point q = Point2{1, 1}; + P projection(ProjectionFactory::instance().get("plate-carree").create(spec::Custom{})); + + EXPECT(points_equal(q, projection->inv(p))); + EXPECT(std::holds_alternative(projection->inv(p))); + + EXPECT(points_equal(p, projection->fwd(q))); + EXPECT(std::holds_alternative(projection->fwd(q))); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/projection_proj.cc b/tests/geo/projection_proj.cc new file mode 100644 index 000000000..8c5c8bd0c --- /dev/null +++ b/tests/geo/projection_proj.cc @@ -0,0 +1,126 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/Projection.h" +#include "eckit/geo/area/BoundingBox.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::util { +area::BoundingBox bounding_box(Point2, Point2, Projection&); +} + + +namespace eckit::geo::test { + + +using P = std::unique_ptr; + + +CASE("projection: proj") { + constexpr double eps = 1e-6; + + + if (ProjectionFactory::instance().exists("proj")) { + PointLonLat a{12., 55.}; + + struct { + const Point b; + const std::string target; + } tests_proj[] = { + {Point2{691875.632137542, 6098907.825129169}, "+proj=utm +zone=32 +datum=WGS84"}, + {Point2{691875.632137542, 6098907.825129169}, "EPSG:32632"}, + {a, "EPSG:4326"}, + {a, "EPSG:4979"}, + {Point3{3586469.6567764, 762327.65877826, 5201383.5232023}, "EPSG:4978"}, + {Point3{3574529.7050235, 759789.74368715, 5219005.2599833}, "+proj=cart +R=6371229."}, + {Point3{3574399.5431832, 759762.07693392, 5218815.216709}, "+proj=cart +ellps=sphere"}, + {a, "+proj=latlon +ellps=sphere"}, + }; + + for (const auto& test : tests_proj) { + P projection(ProjectionFactory::instance().get("proj").create( + spec::Custom{{{"source", "EPSG:4326"}, {"target", test.target}}})); + +#if 0 + Log::info() << "ellipsoid: '" << PROJ::ellipsoid(projection.target()) + << std::endl; +#endif + + auto b = projection->fwd(a); + auto c = projection->inv(b); + + EXPECT(points_equal(b, test.b, eps)); + EXPECT(points_equal(c, a, eps)); + + P reverse(ProjectionFactory::instance().get("proj").create( + spec::Custom({{"source", test.target}, {"target", "EPSG:4326"}}))); + + auto d = reverse->fwd(test.b); + auto e = reverse->inv(d); + + EXPECT(points_equal(d, a, eps)); + EXPECT(points_equal(e, test.b, eps)); + } + + P polar_stereographic_north(ProjectionFactory::instance().get("proj").create( + spec::Custom{{{"source", "EPSG:4326"}, {"target", "+proj=stere +lat_0=90. +lon_0=-30. +R=6371229."}}})); + + P polar_stereographic_south(ProjectionFactory::instance().get("proj").create( + spec::Custom{{{"source", "EPSG:4326"}, {"target", "+proj=stere +lat_0=-90. +lon_0=-30. +R=6371229."}}})); + + struct { + const P& projection; + const Point2 min; + const Point2 max; + const bool periodic; + const bool contains_north_pole; + const bool contains_south_pole; + } tests_bbox[] = { + {polar_stereographic_north, {-2e6, -2e6}, {2e6, 2e6}, true, true, false}, + {polar_stereographic_north, {-2e6, -2e6}, {1e6, 1e6}, true, true, false}, + {polar_stereographic_north, {-2e6, -2e6}, {-1e6, -1e6}, false, false, false}, + {polar_stereographic_north, {-1e6, -1e6}, {2e6, 2e6}, true, true, false}, + {polar_stereographic_north, {-1e6, -1e6}, {1e6, 1e6}, true, true, false}, + {polar_stereographic_north, {1e6, 1e6}, {2e6, 2e6}, false, false, false}, + {polar_stereographic_south, {-2e6, -2e6}, {2e6, 2e6}, true, false, true}, + {polar_stereographic_south, {-2e6, -2e6}, {1e6, 1e6}, true, false, true}, + {polar_stereographic_south, {-2e6, -2e6}, {-1e6, -1e6}, false, false, false}, + {polar_stereographic_south, {-1e6, -1e6}, {2e6, 2e6}, true, false, true}, + {polar_stereographic_south, {-1e6, -1e6}, {1e6, 1e6}, true, false, true}, + {polar_stereographic_south, {1e6, 1e6}, {2e6, 2e6}, false, false, false}, + }; + + for (const auto& test : tests_bbox) { + auto bbox = util::bounding_box(test.min, test.max, *test.projection); + + EXPECT_EQUAL(test.periodic, bbox.periodic()); + EXPECT_EQUAL(test.contains_north_pole, bbox.contains(NORTH_POLE)); + EXPECT_EQUAL(test.contains_south_pole, bbox.contains(SOUTH_POLE)); + + auto global = test.periodic && test.contains_north_pole && test.contains_south_pole; + + EXPECT(global == bbox.global()); + } + } +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/projection_rotation.cc b/tests/geo/projection_rotation.cc new file mode 100644 index 000000000..780706ee4 --- /dev/null +++ b/tests/geo/projection_rotation.cc @@ -0,0 +1,291 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/projection/Composer.h" +#include "eckit/geo/projection/Rotation.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +using P = std::unique_ptr; + + +constexpr double EPS = 1e-6; + + +CASE("rotation (1)") { + spec::Custom spec({ + {"projection", "rotation"}, + {"south_pole_lat", -91.}, + {"south_pole_lon", -361.}, + }); + + Point p = PointLonLat{1, 1}; + P projection(ProjectionFactory::instance().get(spec.get_string("projection")).create(spec)); + + EXPECT(points_equal(p, projection->inv(projection->fwd(p)))); + EXPECT(points_equal(p, projection->fwd(projection->inv(p)))); +} + + +CASE("rotation (2)") { + const PointLonLat p(1, 1); + int delta[] = {-360, -180, -1, 0, 1, 90, 91, 180}; + + for (auto a : delta) { + for (auto b : delta) { + for (auto c : delta) { + projection::Rotation rot(0. + static_cast(b), -90. + static_cast(a), + static_cast(c)); + EXPECT(rot.rotated() == (a % 360 != 0 || (b - c) % 360 != 0)); + + EXPECT(points_equal(p, rot.inv(rot.fwd(p)), EPS)); + EXPECT(points_equal(p, rot.fwd(rot.inv(p)), EPS)); + } + } + } +} + + +CASE("rotation (3)") { + const PointLonLat sp(182., -46.7); + projection::Rotation rot(sp.lon, sp.lat, 0.); + + ASSERT(points_equal(sp.antipode(), PointLonLat{2., 46.7})); + + + SECTION("pole point") { + PointLonLat a(0., 90.); + auto b = rot.fwd(a); + auto c = rot.inv(b); + + EXPECT(points_equal(b, sp.antipode(), EPS)); + EXPECT(points_equal(a, c, EPS)); + } + + + SECTION("many points") { + const int Ni = 12; + const int Nj = 3; + + const PointLonLat ref[]{ + {-178., -46.7}, // + {-178., -16.7}, + {-178., 13.3}, + {-178., 43.3}, + {-178., 73.3}, + {2., 76.7}, + {2., 46.7}, + {-178., -46.7}, + {-162.623427, -19.469294}, + {-152.023657, 8.654593}, + {-139.574639, 36.436827}, + {-113.108943, 61.431994}, + {-39.882454, 68.008245}, + {2., 46.7}, + {-178., -46.7}, + {-148.834426, -27.310675}, + {-129.263456, -3.837005}, + {-110.791162, 20.054216}, + {-85.879167, 41.365070}, + {-44.424956, 53.295076}, + {2., 46.7}, + {-178., -46.7}, + {-137.907940, -39.070023}, + {-109.601456, -21.339065}, + {-88., 0.}, + {-66.398544, 21.339065}, + {-38.092060, 39.070023}, + {2., 46.7}, + {-178., -46.7}, + {-131.575044, -53.295076}, + {-90.120833, -41.365070}, + {-65.208838, -20.054216}, + {-46.736544, 3.837005}, + {-27.165574, 27.310675}, + {2., 46.7}, + {-178., -46.7}, + {-136.117546, -68.008245}, + {-62.891057, -61.431994}, + {-36.425361, -36.436827}, + {-23.976343, -8.654593}, + {-13.376573, 19.469294}, + {2., 46.7}, + {-178., -46.7}, + {-178., -76.7}, + {2., -73.3}, + {2., -43.3}, + {2., -13.3}, + {2., 16.7}, + {2., 46.7}, + {-178., -46.7}, + {140.117546, -68.008245}, + {66.891057, -61.431994}, + {40.425361, -36.436827}, + {27.976343, -8.654593}, + {17.376573, 19.469294}, + {2., 46.7}, + {-178., -46.7}, + {135.575044, -53.295076}, + {94.120833, -41.365070}, + {69.208838, -20.054216}, + {50.736544, 3.837005}, + {31.165574, 27.310675}, + {2., 46.7}, + {-178., -46.7}, + {141.907940, -39.070023}, + {113.601456, -21.339065}, + {92., 0.}, + {70.398544, 21.339065}, + {42.092060, 39.070023}, + {2., 46.7}, + {-178., -46.7}, + {152.834426, -27.310675}, + {133.263456, -3.837005}, + {114.791162, 20.054216}, + {89.879167, 41.365070}, + {48.424956, 53.295076}, + {2., 46.7}, + {-178., -46.7}, + {166.623427, -19.469294}, + {156.023657, 8.654593}, + {143.574639, 36.436827}, + {117.108943, 61.431994}, + {43.882454, 68.008245}, + {2., 46.7}, + }; + + for (int i = 0, k = 0; i < Ni; i++) { + for (int j = 0; j < 2 * Nj + 1; j++, k++) { + PointLonLat a(static_cast(i) * 360. / static_cast(Ni), + static_cast(j - Nj) * 90. / static_cast(Nj)); + auto b = rot.fwd(a); + auto c = rot.inv(b); + + EXPECT(points_equal(b, ref[k], EPS)); + EXPECT(points_equal(a, c, EPS)); + } + } + } +} + + +CASE("rotation (4)") { + const projection::Rotation non_rotated(0., -90., 0.); + const projection::Rotation rotation_angle(0., -90., -180.); + const projection::Rotation rotation_matrix(4., -40., 180.); + + EXPECT(not non_rotated.rotated()); + EXPECT(rotation_angle.rotated()); + EXPECT(rotation_matrix.rotated()); + + const PointLonLat p[] = {{0., 90.}, {0., 0.}, {270., 25.}, {-180., 45.}}; + + struct { + const projection::Rotation& rotation; + const PointLonLat a; + const PointLonLat b; + } tests[] = { + {non_rotated, p[0], p[0]}, + {non_rotated, p[1], p[1]}, + {non_rotated, p[2], p[2]}, + {non_rotated, p[3], p[3]}, + {rotation_angle, p[0], {p[0].lon - 180., p[0].lat}}, + {rotation_angle, p[1], {p[1].lon - 180., p[1].lat}}, + {rotation_angle, p[2], {p[2].lon - 180., p[2].lat}}, + {rotation_angle, p[3], {p[3].lon - 180., p[3].lat}}, + {rotation_matrix, p[0], {-176., 40.}}, + {rotation_matrix, p[1], {-176., -50.}}, + {rotation_matrix, p[2], {113.657357, 15.762700}}, + {rotation_matrix, p[3], {-176., 85.}}, + }; + + for (const auto& test : tests) { + auto b = test.rotation.fwd(test.a); + EXPECT(points_equal(b, test.b, EPS)); + + auto a = test.rotation.inv(b); + EXPECT(points_equal(a, test.a, EPS)); + } +} + + +CASE("rotation (5)") { + spec::Custom spec({ + {"projection", "rotation"}, + {"south_pole_lat", -90.}, + {"south_pole_lon", 0.}, + {"rotation_angle", 45.}, + }); + + // compose sequentially + const auto& builder = ProjectionFactory::instance().get("rotation"); + P composition1(new projection::Composer{ + builder.create(spec), + builder.create(spec), + builder.create(spec), + builder.create(spec), + builder.create(spec), + builder.create(spec), + builder.create(spec), + }); + + dynamic_cast(composition1.get())->emplace_back(builder.create(spec)); + + for (auto lat : {0., 10., -10.}) { + PointLonLat p{0., lat}; + auto q = composition1->fwd(p); + + EXPECT(points_equal(p, q)); + EXPECT(points_equal(p, composition1->inv(q))); + + auto qs = dynamic_cast(composition1.get())->fwd_points(p); + EXPECT(qs.size() == 8); + + EXPECT(points_equal(qs.front(), PointLonLat{-45., lat})); + EXPECT(points_equal(qs[1], PointLonLat{-90., lat})); + EXPECT(points_equal(qs[2], PointLonLat{-135., lat})); + // ... + EXPECT(points_equal(qs.back(), p)); + } + + // compose by nesting + P composition2(builder.create(spec)); + for (size_t i = 1; i < 8; ++i) { + composition2.reset(projection::Composer::compose_back(composition2.release(), spec)); + } + + for (auto lat : {0., 10., -10.}) { + PointLonLat p{0., lat}; + + auto qs1 = dynamic_cast(composition1.get())->fwd_points(p); + auto qs2 = dynamic_cast(composition2.get())->fwd_points(p); + + ASSERT(qs1.size() == 8); + EXPECT(qs2.size() == 2); + EXPECT(points_equal(qs1[6], qs2[0])); + EXPECT(points_equal(qs1[7], qs2[1])); + } +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/range.cc b/tests/geo/range.cc new file mode 100644 index 000000000..aec1104b9 --- /dev/null +++ b/tests/geo/range.cc @@ -0,0 +1,258 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include + +#include "eckit/geo/range/GaussianLatitude.h" +#include "eckit/geo/range/RegularLatitude.h" +#include "eckit/geo/range/RegularLongitude.h" +#include "eckit/testing/Test.h" +#include "eckit/types/FloatCompare.h" +#include "eckit/types/Fraction.h" + + +#define EXPECT_APPROX(a, b) EXPECT(::eckit::types::is_approximately_equal((a), (b), 1e-3)) + + +namespace eckit::geo::test { + + +#if 0 +std::ostream& operator<<(std::ostream& out, const std::vector& v) { + const char* sep = ""; + for (const auto& e : v) { + out << sep << e; + sep = ", "; + } + return out; +} +#endif + + +CASE("range::RegularLongitude") { + SECTION("simple") { + const range::RegularLongitude range1(1., -1., 2.); + + EXPECT(range1.size() == 4); + + EXPECT_APPROX(range1.a(), -1.); + EXPECT_APPROX(range1.b(), 2.); + EXPECT_APPROX(range1.increment(), 1.); + + const auto& values = range1.values(); + + EXPECT_APPROX(values[0], -1.); + EXPECT_APPROX(values[1], 0.); + EXPECT_APPROX(values[2], 1.); + EXPECT_APPROX(values[3], 2.); + + for (const auto& range : { + range::RegularLongitude(1., 0., 360.), + range::RegularLongitude(1., 0.5, 359.5), + range::RegularLongitude(1., 0., 360., 0.5, 0.), + }) { + EXPECT(range.periodic()); + EXPECT(range.size() == 360); + } + } + + + SECTION("degenerate") { + EXPECT_THROWS_AS(range::RegularLongitude(static_cast(0), 0., 0.), eckit::AssertionFailed); + EXPECT_THROWS_AS(range::RegularLongitude(static_cast(0), 0., 10.), eckit::AssertionFailed); + + range::RegularLongitude range1(static_cast(1), 1., 1.); + + EXPECT(range1.size() == 1); + EXPECT(range1.values().front() == 1.); + + range::RegularLongitude range2(static_cast(2), 2., 2.); + + EXPECT(range2.size() == 1); + EXPECT(range2.values().front() == 2.); + } + + + SECTION("range [-90, 90]") { + const range::RegularLongitude range(static_cast(3), -90., 90.); + + EXPECT(range.size() == 3); + + const auto& values = range.values(); + + EXPECT(range.size() == values.size()); + + EXPECT_APPROX(values[0], -90.); + EXPECT_APPROX(values[1], 0.); + EXPECT_APPROX(values[2], 90.); + } + + + SECTION("range [-180, 180]") { + const range::RegularLongitude range1(static_cast(4), -180., 180.); + + EXPECT(range1.size() == 4); + + const auto& values1 = range1.values(); + + EXPECT(range1.size() == values1.size()); + + EXPECT_APPROX(values1[0], -180.); + EXPECT_APPROX(values1[1], -90.); + EXPECT_APPROX(values1[2], 0.); + EXPECT_APPROX(values1[3], 90.); + + const range::RegularLongitude range2(static_cast(8), 180., -180.); + + EXPECT(range2.size() == 8); + + const auto& values2 = range2.values(); + + EXPECT(range2.size() == values2.size()); + + EXPECT_APPROX(values2[0], 180.); + EXPECT_APPROX(values2[1], 135.); + EXPECT_APPROX(values2[2], 90.); + EXPECT_APPROX(values2[3], 45.); + EXPECT_APPROX(values2[7], -135.); + } + + + SECTION("range [0, 360], cropped") { + auto range = range::RegularLongitude(static_cast(36), 0., 360.); + + EXPECT(range.periodic()); + + const std::unique_ptr range1(range.make_range_cropped(-180., 180.)); + + EXPECT(range1->size() == 36); + EXPECT(range1->a() == -180.); + EXPECT(range1->b() == 180.); + EXPECT(range1->periodic()); + + const std::unique_ptr range2(range.make_range_cropped(-180., 170.)); + + EXPECT(range2->size() == 36); + EXPECT(range2->b() == 180.); // because it's how we can distinguish without additional metadata + EXPECT(range2->periodic()); + + const std::unique_ptr range3(range.make_range_cropped(-180., 160.)); + + EXPECT(range3->size() == 36 - 1); + EXPECT(range3->b() == 160.); + EXPECT_NOT(range3->periodic()); + } + + + SECTION("range [0, 180], cropped") { + auto range = range::RegularLongitude(static_cast(19), 0., 180.); + + EXPECT_NOT(range.periodic()); + + const std::unique_ptr range1(range.make_range_cropped(1., 179.)); + + EXPECT(range1->size() == 19 - 2); + EXPECT(range1->a() == 10.); + EXPECT(range1->b() == 170.); + EXPECT_NOT(range1->periodic()); + + const std::unique_ptr range2(range.make_range_cropped(1., 170.)); + + EXPECT(range2->size() == 19 - 2); + EXPECT(range2->a() == 10.); + EXPECT(range2->b() == 170.); + EXPECT_NOT(range2->periodic()); + + const std::unique_ptr range3(range.make_range_cropped(-180., 180.)); + + EXPECT(range3->size() == 19); + EXPECT(range3->a() == 0.); + EXPECT(range3->b() == 180.); + EXPECT_NOT(range3->periodic()); + + const std::unique_ptr range4(range.make_range_cropped(-190., 170.)); + + EXPECT(range4->size() == 19 - 1); + EXPECT(range4->a() == 0.); + EXPECT(range4->b() == 170.); + EXPECT_NOT(range4->periodic()); + } +} + + +CASE("range::RegularLatitude") { + SECTION("simple") { + const range::RegularLatitude range1(1., 90., -90., 0.5); + + EXPECT(range1.size() == 180); + EXPECT(range1.a() == 89.5); + EXPECT(range1.b() == -89.5); + } +} + + +CASE("range::Gaussian") { + std::vector ref{59.44440828916676, 19.87571914744090, -19.87571914744090, -59.44440828916676}; + + + SECTION("global") { + auto global = range::GaussianLatitude(2, false); + EXPECT(global.size() == ref.size()); + + size_t i = 0; + for (const auto& test : global.values()) { + EXPECT_APPROX(test, ref[i++]); + } + } + + + SECTION("crop [50., -50.]") { + std::unique_ptr cropped(range::GaussianLatitude(2, false).make_range_cropped(50., -50.)); + EXPECT(cropped->size() == ref.size() - 2); + + EXPECT_APPROX(cropped->values()[0], ref[1]); + EXPECT_APPROX(cropped->values()[1], ref[2]); + + EXPECT( + std::unique_ptr(range::GaussianLatitude(2, false, 1e-3).make_range_cropped(59.444, -59.444))->size() + == 4); + EXPECT( + std::unique_ptr(range::GaussianLatitude(2, false, 1e-6).make_range_cropped(59.444, -59.444))->size() + == 2); + EXPECT( + std::unique_ptr(range::GaussianLatitude(2, false, 1e-6).make_range_cropped(59.444, -59.445))->size() + == 3); + + std::unique_ptr single(range::GaussianLatitude(2, false, 1e-3).make_range_cropped(-59.444, -59.444)); + EXPECT(single->size() == 1); + + EXPECT_APPROX(single->values().front(), ref.back()); + } + + + SECTION("crop [90., 0.]") { + std::unique_ptr cropped(range::GaussianLatitude(2, false, 1e-3).make_range_cropped(90., 0.)); + EXPECT(cropped->size() == ref.size() / 2); + + EXPECT_APPROX(cropped->values()[0], ref[0]); + EXPECT_APPROX(cropped->values()[1], ref[1]); + } +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/search.cc b/tests/geo/search.cc new file mode 100644 index 000000000..dd84d97b0 --- /dev/null +++ b/tests/geo/search.cc @@ -0,0 +1,75 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/geo/Search.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("Search2") { + std::vector points{ + {{0., 0.}, 0}, + }; + + Search2 search; + search.build(points); + + auto a = search.nearestNeighbour({0.1, 0.}); + EXPECT_EQUAL(a.payload(), 0); + + auto b = search.nearestNeighbour({0.9, 0.}); + EXPECT_EQUAL(b.payload(), 0); + + search.insert({{1., 0.}, 1}); + + auto c = search.nearestNeighbour({0.1, 0.}); + EXPECT_EQUAL(c.payload(), 0); + + auto d = search.nearestNeighbour({0.9, 0.}); + EXPECT_EQUAL(d.payload(), 1); +} + + +CASE("Search3") { + std::vector points{ + {{0., 0., 0.}, 0}, + }; + + Search3 search; + search.build(points); + + auto a = search.nearestNeighbour({0.1, 0., 0.}); + EXPECT_EQUAL(a.payload(), 0); + + auto b = search.nearestNeighbour({0.9, 0., 0.}); + EXPECT_EQUAL(b.payload(), 0); + + search.insert({{1., 0., 0.}, 1}); + + auto c = search.nearestNeighbour({0.1, 0., 0.}); + EXPECT_EQUAL(c.payload(), 0); + + auto d = search.nearestNeighbour({0.9, 0., 0.}); + EXPECT_EQUAL(d.payload(), 1); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/spec.cc b/tests/geo/spec.cc new file mode 100644 index 000000000..5b67f9762 --- /dev/null +++ b/tests/geo/spec.cc @@ -0,0 +1,161 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include +#include +#include + +#include "eckit/geo/Grid.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/geo/util.h" +#include "eckit/log/Log.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("user -> type") { + using v = std::vector; + + static const spec::Custom::container_type BAD; + ASSERT(BAD.empty()); + + static std::pair tests[]{ + {{{"N", 2}}, {{"N", 2}, {"type", "reduced_gg"}}}, + {{{"area", v{90, -180, -90, 180}}, {"grid", v{2, 2}}}, {{"type", "regular_ll"}}}, + {{{"area", v{90, -180, -90, 180}}}, BAD}, + {{{"grid", "B48"}}, BAD}, + {{{"grid", "F48"}}, {{"type", "regular_gg"}}}, + {{{"grid", "N48"}}, {{"type", "reduced_gg"}}}, + {{{"grid", "O48"}}, {{"type", "reduced_gg"}}}, + {{{"grid", 48}}, BAD}, + {{{"grid", v{2, 2}}}, {{"grid", v{2, 2}}, {"type", "regular_ll"}}}, + {{{"type", "latlon"}, {"grid", v{2, 2}}}, BAD}, + {{{"type", "reduced_gg"}, {"grid", "48"}}, BAD}, + {{{"type", "reduced_gg"}, {"grid", "F048"}}, BAD}, + {{{"type", "reduced_gg"}, {"grid", "N"}}, BAD}, + {{{"type", "reduced_gg"}, {"grid", "N048"}}, BAD}, + {{{"type", "reduced_gg"}, {"grid", "N48"}}, {{"type", "reduced_gg"}}}, + {{{"type", "reduced_gg"}, {"grid", "O048"}}, BAD}, + {{{"type", "reduced_gg"}, {"grid", "O48"}}, {{"type", "reduced_gg"}}}, + {{{"type", "reduced_gg"}, {"grid", 48}}, BAD}, + {{{"type", "reduced_gg"}}, BAD}, + {{{"type", "reduced_latlon"}, {"grid", 2}}, BAD}, + {{{"type", "reduced_ll"}, {"grid", 12}}, BAD}, + {{{"type", "regular_gg"}, {"grid", "48"}}, BAD}, + {{{"type", "regular_gg"}, {"grid", "F048"}}, BAD}, + {{{"type", "regular_gg"}, {"grid", "F48"}}, {{"type", "regular_gg"}}}, + {{{"type", "regular_gg"}, {"grid", "N48"}}, BAD}, + {{{"type", "regular_gg"}, {"grid", "O48"}}, BAD}, + {{{"type", "regular_gg"}, {"grid", "a"}}, BAD}, + {{{"type", "regular_gg"}, {"grid", 48}}, BAD}, + {{{"type", "regular_ll"}, {"area", v{90, -180, -90, 180}}}, BAD}, + {{{"type", "regular_ll"}, {"grid", "F48"}}, BAD}, + {{{"type", "regular_ll"}, {"grid", "a"}}, BAD}, + {{{"type", "regular_ll"}, {"grid", 48}}, BAD}, + {{{"type", "regular_ll"}, {"grid", std::vector{"a", "b"}}}, BAD}, + {{{"type", "regular_ll"}, {"grid", v{1, 2, 3}}}, BAD}, + {{{"type", "regular_ll"}, {"grid", v{1, 2}}}, {{"type", "regular_ll"}}}, + {{{"type", "regular_ll"}, {"grid", v{1}}}, BAD}, + + {{{"type", "mercator"}, + {"area", v{31.173058, 262.036499, 14.736453, 284.975281}}, + {"grid", v{45000.0, 45000.0}}, + {"shape", std::vector{56, 44}}, + {"lad", 14.0}, + {"orientation", 0.0}}, + {}}, + }; + + for (const auto& [user, ref] : tests) { + spec::Custom userspec(user); + spec::Custom refspec(ref); + + Log::info() << userspec << " -> " << refspec << std::endl; + + try { + std::unique_ptr spec(GridFactory::make_spec(userspec)); + EXPECT(spec); + + std::unique_ptr grid(GridFactory::build(*spec)); + EXPECT(grid); + } + catch (const SpecNotFound& e) { + EXPECT(refspec.empty() /*BAD*/); + } + catch (const BadParameter& e) { + EXPECT(refspec.empty() /*BAD*/); + } + } +} + + +CASE("grid: name -> spec -> grid: name") { +// FIXME +#if 0 + for (const std::string& name : {"LAEA-EFAS-5km", "SMUFF-OPERA-2km"}) { + std::unique_ptr grid(GridFactory::build(spec::Custom({{"grid", name}}))); + EXPECT(grid); + + auto gridspec = grid->spec_str(); + EXPECT(gridspec == R"({"grid":")" + name + R"("})"); + } +#endif +} + + +CASE("grid: reduced_gg") { + std::unique_ptr o16(GridFactory::build(spec::Custom({{"grid", "o16"}}))); + + EXPECT(o16->spec_str() == R"({"grid":"O16"})"); + + std::unique_ptr n16(GridFactory::build(spec::Custom({{"grid", "n16"}}))); + + EXPECT(n16->spec_str() == R"({"grid":"N16"})"); + + std::unique_ptr known_pl_1(GridFactory::build( + spec::Custom({{"pl", pl_type{20, 27, 32, 40, 45, 48, 60, 60, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 60, 60, 48, 45, 40, 32, 27, 20}}}))); + + EXPECT(known_pl_1->spec_str() == R"({"grid":"N16"})"); + + std::unique_ptr known_pl_2( + GridFactory::build(spec::Custom({{"pl", pl_type{20, 24, 28, 32, 32, 28, 24, 20}}}))); + + EXPECT(known_pl_2->spec_str() == R"({"grid":"O4"})"); + + std::unique_ptr unknown_pl( + GridFactory::build(spec::Custom({{"pl", pl_type{20, 24, 28, 32, 32, 28, 24, 99}}}))); + + EXPECT(unknown_pl->spec_str() == R"({"grid":"N4","pl":[20,24,28,32,32,28,24,99]})"); +} + + +CASE("grid: HEALPix") { + std::unique_ptr h2(GridFactory::build(spec::Custom({{"grid", "h2"}}))); + + EXPECT(h2->spec_str() == R"({"grid":"H2","ordering":"ring"})"); + + std::unique_ptr h2n(GridFactory::build(spec::Custom({{"grid", "H2"}, {"ordering", "nested"}}))); + + EXPECT(h2n->spec_str() == R"({"grid":"H2","ordering":"nested"})"); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/spec_custom.cc b/tests/geo/spec_custom.cc new file mode 100644 index 000000000..df9dae950 --- /dev/null +++ b/tests/geo/spec_custom.cc @@ -0,0 +1,365 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/parser/YAMLParser.h" +#include "eckit/testing/Test.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::geo::test { + + +using spec::Custom; + + +template +struct is_vector : std::false_type {}; + + +template +struct is_vector> : std::true_type {}; + + +template +constexpr bool is_vector_v = is_vector::value; + + +template +void test_t() { + T a; + T b; + T c; + + if constexpr (std::is_same_v>) { + a = b = {"1", "2", "3"}; + c = {"7", "8", "9", "10"}; + } + else if constexpr (std::is_same_v) { + a = b = "1"; + c = "7"; + } + else if constexpr (is_vector_v) { + a = b = {1, 2, 3}; + c = {7, 8, 9, 10}; + } + else { + a = b = 1; + c = 7; + } + + EXPECT_NOT(a != b); + EXPECT(a == b); + EXPECT_NOT(a < b); + EXPECT(a <= b); + EXPECT_NOT(a > b); + EXPECT(a >= b); + + EXPECT(a != c); + EXPECT_NOT(a == c); + EXPECT(a < c); + EXPECT(a <= c); + EXPECT_NOT(a > c); + EXPECT_NOT(a >= c); +} + + +CASE("Custom::value_type") { + test_t(); + // test_t(); + test_t(); + test_t(); + test_t(); + test_t(); + test_t(); + test_t(); + test_t>(); + test_t>(); + test_t>(); + test_t>(); + test_t>(); + test_t>(); + test_t>(); +} + + +CASE("Custom::container_type") { + Custom spec({{std::string{"foo"}, "bar"}}); + + std::string bar; + EXPECT(spec.get("foo", bar) && bar == "bar"); +} + + +CASE("Custom::custom_type") { + Custom custom1({{"custom1", Custom::custom_ptr(new Custom({{"foo", "bar"}, {"boo", "far"}}))}}); + + EXPECT(custom1.str() == R"({"custom1":{"boo":"far","foo":"bar"}})"); + + Custom custom2({{"custom2", Custom::custom_ptr(new Custom(custom1.container()))}}); + + EXPECT(custom2.str() == R"({"custom2":{"custom1":{"boo":"far","foo":"bar"}}})"); + + Custom customer1({{"customer", Custom::custom_ptr(Custom::make_from_value( + YAMLParser::decodeString("{name: John Smith, age: 33}")))}}); + + EXPECT(customer1.str() == R"({"customer":{"age":33,"name":"John Smith"}})"); + + Custom customer2({{"customer", Custom::custom_ptr(Custom::make_from_value(YAMLParser::decodeString(R"( +name: John Smith +age: 33 +)")))}}); + + EXPECT(customer2.str() == customer1.str()); + + Custom nested({{"a", Custom::custom_ptr(Custom::make_from_value(YAMLParser::decodeString(R"( +b: + c: 1 + d: "2" +)")))}}); + + EXPECT(nested.str() == R"({"a":{"b":{"c":1,"d":"2"}}})"); + + EXPECT(nested.has_custom("a")); + EXPECT_NOT(nested.has_custom("a?")); + EXPECT_THROWS_AS(nested.custom("a?"), SpecNotFound); + + EXPECT(nested.custom("a")->has_custom("b")); + EXPECT_NOT(nested.custom("a")->has_custom("b?")); + EXPECT_THROWS_AS(nested.custom("a")->custom("b?"), SpecNotFound); + + const auto& b = nested.custom("a")->custom("b"); + ASSERT(b); + + int c = 0; + std::string d; + + EXPECT(b->get("c", c) && c == 1); + EXPECT(b->get("d", d) && d == "2"); +} + + +CASE("Custom::operator==") { + Custom a({{"foo", "bar"}, {"boo", "far"}}); + Custom b({{"bOo", "far"}, {"Foo", "bar"}}); + Custom c({{"foo", "bar"}, {"boo", "faR"}}); + + EXPECT(a == b); + EXPECT(a != c); + EXPECT_NOT(a != b); + EXPECT_NOT(a == c); + + // not commutative + Custom d({{"foo", "bar"}, {"boo", "far"}, {"roo", "fab"}}); + + EXPECT(a == d); + EXPECT(d != a); + EXPECT_NOT(a != d); + EXPECT_NOT(d == a); +} + + +CASE("Spec <- Custom") { + constexpr int zero = 0; + constexpr int one = 1; + constexpr double two = 2.; + const std::string three = "3"; + + + SECTION("access") { + std::unique_ptr spec(new Custom({{"a", -123}, {"b", "B"}, {"c", 123UL}})); + + int a = 0; + EXPECT(spec->get("a", a)); + EXPECT_EQUAL(a, -123); + + std::string b; + EXPECT(spec->get("b", b)); + EXPECT_EQUAL(b, "B"); + + size_t c = 0; + EXPECT(spec->get("c", c)); + EXPECT_EQUAL(c, 123UL); + + std::string b2; + EXPECT(spec->get("B", b2)); + EXPECT_EQUAL(b2, b); + + int d = 0; + dynamic_cast(spec.get())->set("B", 321); + EXPECT(spec->get("b", d)); + EXPECT_EQUAL(d, 321); + } + + + SECTION("conversion (1)") { + Custom a({ + {"double", static_cast(one)}, + {"float", static_cast(one)}, + {"int", static_cast(one)}, + {"long", static_cast(one)}, + {"size_t", static_cast(one)}, + }); + + // test scalar type conversion + for (const std::string& key : {"double", "float", "int", "long", "size_t"}) { + double value_as_double = 0; + float value_as_float = 0; + + EXPECT(a.get(key, value_as_double) && value_as_double == static_cast(one)); + EXPECT(a.get(key, value_as_float) && value_as_float == static_cast(one)); + + if (key == "int" || key == "long" || key == "size_t") { + int value_as_int = 0; + long value_as_long = 0; + size_t value_as_size_t = 0; + + EXPECT(a.get(key, value_as_int) && value_as_int == static_cast(one)); + EXPECT(a.get(key, value_as_long) && value_as_long == static_cast(one)); + EXPECT(a.get(key, value_as_size_t) && value_as_size_t == static_cast(one)); + + EXPECT(a.get_string(key) == std::to_string(1)); + } + else { + EXPECT(a.get_string(key) == std::to_string(1.)); + } + } + } + + + SECTION("conversion (2)") { + Custom b({ + {"true", true}, + {"false", false}, + {"zero", 0}, + {"one", 1}, + }); + + EXPECT(b.get_bool("true")); + EXPECT(!b.get_bool("false")); + + bool maybe = false; + EXPECT(!b.has("?")); + EXPECT(!b.get_bool("?", false)); + EXPECT(b.get_bool("?", true)); + + EXPECT(b.get("true", maybe = false) && maybe); + EXPECT(b.get_bool("true", true)); + EXPECT(b.get_bool("true", false)); + + EXPECT(b.get("false", maybe = true) && !maybe); + EXPECT(!b.get_bool("false", true)); + EXPECT(!b.get_bool("false", false)); + + EXPECT(!b.get_bool("zero")); + EXPECT(!b.get_bool("zero", maybe = true)); + EXPECT(b.get("zero", maybe = true) && !maybe); + + EXPECT(b.get_bool("one")); + EXPECT(b.get_bool("one", maybe = false)); + EXPECT(b.get("one", maybe = false) && maybe); + } + + + SECTION("conversion (3)") { + Custom c; + EXPECT_NOT(c.has("foo")); + + c.set("foo", two); + EXPECT(c.has("foo")); + EXPECT_THROWS_AS(c.get_int("foo"), SpecNotFound); // cannot access as int + EXPECT(::eckit::types::is_approximately_equal(c.get_double("foo"), two)); + EXPECT(c.get_string("foo") == std::to_string(two)); + + c.set("bar", one); + EXPECT(c.get_int("bar") == one); + EXPECT(::eckit::types::is_approximately_equal(c.get_double("bar"), static_cast(one))); + EXPECT(c.get_string("bar") == "1"); + + c.set("foo", three); + EXPECT(c.get_string("foo") == three); + + + Custom d(c.container()); + + EXPECT(d.has("foo")); + EXPECT(d.get_string("foo") == three); + EXPECT_THROWS_AS(d.get_int("foo"), SpecNotFound); // cannot access as int + EXPECT_THROWS_AS(d.get_double("foo"), SpecNotFound); // cannot access as real + + d.set("foo", one); + EXPECT(d.get_int("foo") == one); + + + Custom e(d.container()); + + ASSERT(e.has("foo")); + ASSERT(e.has("bar")); + } + + + SECTION("conversion (4)") { + Custom e({{"zero", zero}, {"one", one}, {"two", two}}); + + bool maybe = true; + EXPECT(!e.get("?", maybe) && maybe); // non-existant key + EXPECT(!e.get("two", maybe) && maybe); // non-convertible + + EXPECT(e.get("zero", maybe) && !maybe); + EXPECT(e.get("one", maybe) && maybe); + } + + + SECTION("json") { + // test ordering + std::unique_ptr a(new Custom({{"c", "c"}, {"a", "a"}, {"b", 1}})); + + const std::string a_str = a->str(); + const std::string a_ref = R"({"a":"a","b":1,"c":"c"})"; + EXPECT_EQUAL(a_str, a_ref); + + // test types + std::unique_ptr b(new Custom({{"string", "string"}, + {"bool", true}, + {"int", static_cast(1)}, + {"long", static_cast(2)}, + {"long long", static_cast(3)}, + {"size_t", static_cast(4)}, + {"float", static_cast(5)}, + {"double", static_cast(6)}, + {"vector", std::vector{1, 1}}, + {"vector", std::vector{2, 2}}, + {"vector", std::vector{3, 3}}, + {"vector", std::vector{4, 4}}, + {"vector", std::vector{5, 5}}, + {"vector", std::vector{6, 6}}, + {"vector", std::vector{"string", "string"}}})); + + const std::string b_str = b->str(); + const std::string b_ref + = R"({"bool":true,"double":6,"float":5,"int":1,"long":2,"long long":3,"size_t":4,"string":"string","vector":[6,6],"vector":[5,5],"vector":[1,1],"vector":[3,3],"vector":[2,2],"vector":[4,4],"vector":["string","string"]})"; + EXPECT_EQUAL(b_str, b_ref); + } +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/spec_layered.cc b/tests/geo/spec_layered.cc new file mode 100644 index 000000000..9b405d765 --- /dev/null +++ b/tests/geo/spec_layered.cc @@ -0,0 +1,53 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/spec/Custom.h" +#include "eckit/geo/spec/Layered.h" +#include "eckit/testing/Test.h" + + +namespace eckit::geo::test { + + +CASE("Spec <- Layered") { + int one = 1; + double two = 2.; + + spec::Custom a({{"foo", one}, {"bar", two}}); + ASSERT(a.has("foo")); + ASSERT(a.has("bar")); + + spec::Layered b(a); + + ASSERT(b.has("foo")); + ASSERT(b.has("bar")); + + b.hide("foo"); + EXPECT_NOT(b.has("foo")); + + b.unhide("foo"); + ASSERT(b.has("foo")); + + EXPECT(a.get_int("foo") == one); + + auto value = b.get_int("foo"); + EXPECT(value == one); +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geo/util.cc b/tests/geo/util.cc new file mode 100644 index 000000000..24cd74152 --- /dev/null +++ b/tests/geo/util.cc @@ -0,0 +1,130 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include +#include +#include + +#include "eckit/geo/util.h" +#include "eckit/testing/Test.h" +#include "eckit/types/FloatCompare.h" + + +template +bool is_approximately_equal_vector(const std::vector& a, const std::vector& b, double eps = 0.) { + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [=](const T& a, const T& b) { + return ::eckit::types::is_approximately_equal(a, b, eps); + }); +} + + +template +std::ostream& operator<<(std::ostream& out, const std::vector& v) { + const auto* sep = ""; + for (const auto& value : v) { + out << sep << value; + sep = ", "; + } + return out; +} + + +namespace eckit::geo::test { + + +constexpr double EPS = 1e-9; + + +CASE("eckit::geo::util::linspace") { + EXPECT(is_approximately_equal_vector(util::linspace(1, 2, 1, false), std::vector{1.}, EPS)); + EXPECT(is_approximately_equal_vector(util::linspace(1, 1, 2, true), std::vector{1., 1.}, EPS)); + EXPECT(is_approximately_equal_vector(util::linspace(1, 2, 2, true), std::vector{1., 2.}, EPS)); +} + + +CASE("eckit::geo::util::arange") { + EXPECT(is_approximately_equal_vector(util::arange(1, 2, 0.5), std::vector{1, 1.5, 2}, EPS)); +} + + +CASE("eckit::geo::util::gaussian_latitudes") { + std::vector lats_decreasing{ + 88.9277, 87.5387, 86.1415, 84.7424, 83.3426, 81.9425, 80.5421, 79.1417, 77.7412, 76.3406, 74.94, + 73.5394, 72.1387, 70.7381, 69.3374, 67.9367, 66.536, 65.1353, 63.7345, 62.3338, 60.9331, 59.5323, + 58.1316, 56.7309, 55.3301, 53.9294, 52.5286, 51.1279, 49.7271, 48.3264, 46.9256, 45.5249, 44.1241, + 42.7233, 41.3226, 39.9218, 38.5211, 37.1203, 35.7195, 34.3188, 32.918, 31.5172, 30.1165, 28.7157, + 27.315, 25.9142, 24.5134, 23.1127, 21.7119, 20.3111, 18.9104, 17.5096, 16.1088, 14.7081, 13.3073, + 11.9065, 10.5058, 9.10499, 7.70422, 6.30345, 4.90269, 3.50192, 2.10115, 0.700384, -0.700384, -2.10115, + -3.50192, -4.90269, -6.30345, -7.70422, -9.10499, -10.5058, -11.9065, -13.3073, -14.7081, -16.1088, -17.5096, + -18.9104, -20.3111, -21.7119, -23.1127, -24.5134, -25.9142, -27.315, -28.7157, -30.1165, -31.5172, -32.918, + -34.3188, -35.7195, -37.1203, -38.5211, -39.9218, -41.3226, -42.7233, -44.1241, -45.5249, -46.9256, -48.3264, + -49.7271, -51.1279, -52.5286, -53.9294, -55.3301, -56.7309, -58.1316, -59.5323, -60.9331, -62.3338, -63.7345, + -65.1353, -66.536, -67.9367, -69.3374, -70.7381, -72.1387, -73.5394, -74.94, -76.3406, -77.7412, -79.1417, + -80.5421, -81.9425, -83.3426, -84.7424, -86.1415, -87.5387, -88.9277}; + + std::vector lats_increasing(lats_decreasing); + std::reverse(lats_increasing.begin(), lats_increasing.end()); + + EXPECT(is_approximately_equal_vector(util::gaussian_latitudes(64, false), lats_decreasing, 1e-4)); + EXPECT(is_approximately_equal_vector(util::gaussian_latitudes(64, true), lats_increasing, 1e-4)); +} + + +CASE("eckit::geo::util::reduced_classical_pl") { + const pl_type pl{20, 27, 32, 40, 45, 48, 60, 60, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 60, 60, 48, 45, 40, 32, 27, 20}; + EXPECT(is_approximately_equal_vector(util::reduced_classical_pl(16), pl)); + EXPECT(is_approximately_equal_vector(util::reduced_classical_pl(16), pl)); +} + + +CASE("eckit::geo::util::reduced_octahedral_pl") { + const pl_type pl{20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, + 80, 76, 72, 68, 64, 60, 56, 52, 48, 44, 40, 36, 32, 28, 24, 20}; + EXPECT(is_approximately_equal_vector(util::reduced_octahedral_pl(16), pl)); + EXPECT(is_approximately_equal_vector(util::reduced_octahedral_pl(16), pl)); +} + + +CASE("eckit::geo::util::monotonic_crop") { + struct test { + double min; + double max; + std::pair expected; + std::vector values; + std::vector cropped; + } tests[]{ + {1., 1., {0, 1}, {1.}, {1.}}, + {1., 2., {0, 3}, {1., 1., 1.}, {1., 1., 1.}}, + {2., 3., {1, 3}, {1., 2., 3., 4., 5., 6.}, {2., 3.}}, + {2., 3., {3, 5}, {6., 5., 4., 3., 2., 1.}, {3., 2.}}, + }; + + for (const auto& test : tests) { + auto [from, to] = util::monotonic_crop(test.values, test.min, test.max, 0.); + + EXPECT(from == test.expected.first); + EXPECT(to == test.expected.second); + + EXPECT(is_approximately_equal_vector(std::vector(test.values.begin() + from, test.values.begin() + to), + test.cropped)); + } +} + + +} // namespace eckit::geo::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/geometry/test_coordinate_helpers.cc b/tests/geometry/test_coordinate_helpers.cc index 585fe4130..aa0da5607 100644 --- a/tests/geometry/test_coordinate_helpers.cc +++ b/tests/geometry/test_coordinate_helpers.cc @@ -5,6 +5,8 @@ * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. */ +#include + #include "eckit/geometry/CoordinateHelpers.h" #include "eckit/geometry/Point2.h" #include "eckit/testing/Test.h" @@ -21,8 +23,95 @@ CASE("normalise angles") { EXPECT(14. == normalise_angle(374., 0.)); EXPECT(14. == normalise_angle(374., -90.)); EXPECT(374. == normalise_angle(374., 90.)); + EXPECT(14. == normalise_angle(-346., 0.)); + EXPECT(14. == normalise_angle(-346., -90.)); + EXPECT(374. == normalise_angle(-346., 90.)); + EXPECT(0. == normalise_angle(360. * 1e12, 0.)); + EXPECT(14. == normalise_angle(360. * 1e12 + 14, 0.)); + EXPECT(0. == normalise_angle(-360. * 1e12, 0.)); + EXPECT(14. == normalise_angle(-360. * 1e12 + 14, 0.)); +} + +CASE("normalise angles that should stay bit-identical") { + + std::vector lats{ + 0x1.a99999999999fp+3, // 13.3 + 0x1.7599999999999p+5, // 46.7 + -0x1.37823af2187f7p+4, // -19.4692944962371 + 0x1.14f26c8adc252p+3, // 8.65459277268488 + 0x1.237e9f537dd2dp+5, // 36.4368273279928 + 0x1.eb74b977e1e89p+5, // 61.431994377691 + 0x1.1008717af4f67p+6, // 68.0082453929914 + -0x1.b4f88656270d9p+4, // -27.3106749883017 + -0x1.eb22f87f6ac12p+1, // -3.83700472089323 + 0x1.40de11e0c3e99p+4, // 20.0542162685293 + 0x1.4aeba99be1331p+5, // 41.3650695970631 + 0x1.aa5c50f727ae6p+5, // 53.2950763043389 + -0x1.556ccf04ef1bbp+4, // -21.3390646164641 + 0x1.556ccf04ef1bbp+4, // 21.3390646164641 + 0x1.388f683df92bbp+5, // 39.070023044745 + -0x1.40de11e0c3e9dp+4, // -20.0542162685293 + 0x1.eb22f87f6abf5p+1, // 3.83700472089321 + 0x1.b4f88656270d7p+4, // 27.3106749883017 + }; + for(const auto& lat: lats) { + double normalised = normalise_angle(lat,-90.); + // std::cout << std::hexfloat << lat << "\t : " << std::defaultfloat << std::setprecision(17) << lat << std::endl; + // std::cout << std::hexfloat << normalised << "\t : " << std::defaultfloat << std::setprecision(17) << normalised << std::endl; + EXPECT_EQUAL(normalised, lat); + } + std::vector lons{ + -0x1.3f0f4411db559p+5, // -39.8824540515004 + -0x1.63664f7d2181dp+5, // -44.4249563003398 + -0x1.75e470fd085aap+5, // -46.7365436332869 + -0x1.b2a6314996231p+4, // -27.1655743479225 + -0x1.f720e2a9525edp+5, // -62.8910573223364 + -0x1.236723c039272p+5, // -36.425361158127 + -0x1.7f9f1a40a5d1fp+4, // -23.9763433957388 + 0x1.ffffffffffffep+0, // 2.0 + 0x1.0b907154a92f7p+6, // 66.8910573223364 + 0x1.436723c039272p+5, // 40.425361158127 + 0x1.bf9f1a40a5d1fp+4, // 27.9763433957388 + 0x1.0f266c20b79f9p+7, // 135.57504369966 + 0x1.787bbbb54c676p+6, // 94.1208332374461 + 0x1.95e470fd085aap+5, // 50.7365436332869 + 0x1.1bd0dd7b42b69p+7, // 141.907939769643 + 0x1.19981bd70b549p+6, // 70.3985437012353 + 0x1.50bc8a12f525bp+5, // 42.0920602303565 + 0x1.cb2a2664f7bbdp+6, // 114.791162088032 + 0x1.6784444ab398ap+6, // 89.8791667625539 + 0x1.83664f7d2181ep+5, // 48.4249563003398 + 0x1.380c1cb7eb45dp+7, // 156.023656604261 + 0x1.d46f8eab56d0bp+6, // 117.108942677664 + 0x1.5f0f4411db559p+5, // 43.8824540515004 + }; + for(const auto& lon: lons) { + double normalised = normalise_angle(lon,-180.); + // std::cout << std::hexfloat << lon << "\t : " << std::defaultfloat << std::setprecision(17) << lon << std::endl; + // std::cout << std::hexfloat << normalised << "\t : " << std::defaultfloat << std::setprecision(17) << normalised << std::endl; + EXPECT_EQUAL(normalised, lon); + } } +CASE("normalise angles that should be reproducible") { + constexpr double minimum = -90.; + std::vector> reproduce{ + // angle normalised + {0x1.a99999999999fp+3, 0x1.a99999999999fp+3}, + {0x1.a99999999999fp+3-360., 0x1.a9999999999ap+3 }, + {0x1.a99999999999fp+3-720., 0x1.a99999999998p+3 }, + {0x1.a99999999999fp+3+360., 0x1.a9999999999ap+3 }, + {0x1.a99999999999fp+3+720., 0x1.a99999999998p+3 }, + }; + for (const auto& [angle, normalised_ref] : reproduce) { + double normalised = normalise_angle(angle, minimum); + // std::cout << std::hexfloat << angle << "\t : " << std::defaultfloat << std::setprecision(17) << angle << std::endl; + // std::cout << std::hexfloat << normalised << "\t : " << std::defaultfloat << std::setprecision(17) << normalised << std::endl; + EXPECT(normalised == normalised_ref); + } +} + + CASE("canonicalise on sphere") { using types::is_approximately_equal; diff --git a/tests/large_file/test_large_file.cc b/tests/large_file/test_large_file.cc index 50c5aa9f1..bfd92181a 100644 --- a/tests/large_file/test_large_file.cc +++ b/tests/large_file/test_large_file.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include #include #include diff --git a/tests/log/CMakeLists.txt b/tests/log/CMakeLists.txt index fbd95d313..58f9346bc 100644 --- a/tests/log/CMakeLists.txt +++ b/tests/log/CMakeLists.txt @@ -27,3 +27,7 @@ ecbuild_add_test( TARGET eckit_test_log_user_channels ENABLED OFF SOURCES test_log_user_channels.cc LIBS eckit ) + +ecbuild_add_test( TARGET eckit_test_syslog + SOURCES test_syslog.cc + LIBS eckit ) \ No newline at end of file diff --git a/tests/log/test_syslog.cc b/tests/log/test_syslog.cc new file mode 100644 index 000000000..ab602a23a --- /dev/null +++ b/tests/log/test_syslog.cc @@ -0,0 +1,84 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + +#include + +#include "eckit/log/SysLog.h" +#include "eckit/testing/Test.h" + +using namespace std; +using namespace eckit; +using namespace eckit::testing; + +namespace eckit::test { + +//---------------------------------------------------------------------------------------------------------------------- + +CASE("test_priority") { + SysLog log("Test message", 0, SysLog::Local7, SysLog::Info); + std::string logString = static_cast(log); + std::string expectedPriority = "<" + std::to_string(log.priority()) + ">"; + EXPECT(logString.find(expectedPriority) != std::string::npos); +} + +//---------------------------------------------------------------------------------------------------------------------- + +CASE("test_timezone") { + SysLog log("Test message", 0, SysLog::Local7, SysLog::Info); + std::string logString = static_cast(log); + // Check if 'Z' UTC indicator is present + EXPECT(logString.find("Z") != std::string::npos); + // Check if 'T' separator is present + EXPECT(logString.find("T") != std::string::npos); +} + +//---------------------------------------------------------------------------------------------------------------------- + +CASE("test_appname") { + SysLog log("Test message", 0, SysLog::Local7, SysLog::Info); + EXPECT(log.appName() == Main::instance().name()); + + // Change the appName and check if it persists + log.appName("test_app"); + std::string logString = static_cast(log); + EXPECT(logString.find("test_app") != std::string::npos); + + // Create a new SysLog instance and check if it retains the original appName + SysLog newLog("New message", 2, SysLog::Local7, SysLog::Info); + EXPECT(newLog.appName() == Main::instance().name()); +} + +//---------------------------------------------------------------------------------------------------------------------- + +CASE("test_without_structured_data") { + SysLog log("Test message", 0, SysLog::Local7, SysLog::Info); + std::string logString = static_cast(log); + // Check if structured data is not present + EXPECT(logString.find("[origin") == std::string::npos); +} + +//---------------------------------------------------------------------------------------------------------------------- +CASE("test_with_structured_data") { + SysLog log("Test message", 0, SysLog::Local7, SysLog::Info); + log.software("log_test"); + log.swVersion("1.0.0"); + log.enterpriseId("7464"); + std::string logString = static_cast(log); + EXPECT(logString.find("enterpriseId=\"7464\"") != std::string::npos); + EXPECT(logString.find("software=\"log_test\"") != std::string::npos); + EXPECT(logString.find("swVersion=\"1.0.0\"") != std::string::npos); +} +//---------------------------------------------------------------------------------------------------------------------- + +} // namespace eckit::test + +int main(int argc, char** argv) { + return run_tests(argc, argv); +} diff --git a/tests/maths/CMakeLists.txt b/tests/maths/CMakeLists.txt index 797a792dd..90a0b4de3 100644 --- a/tests/maths/CMakeLists.txt +++ b/tests/maths/CMakeLists.txt @@ -1,9 +1,22 @@ -ecbuild_add_test( TARGET eckit_test_maths_eigen - SOURCES test_eigen.cc - CONDITION HAVE_EIGEN - LIBS eckit_maths eckit +ecbuild_add_test( + TARGET eckit_test_maths_eigen + SOURCES test_eigen.cc + CONDITION HAVE_EIGEN + LIBS eckit_maths eckit ) -ecbuild_add_test( TARGET eckit_test_maths_matrix - SOURCES test_matrix.cc - LIBS eckit_maths eckit + +foreach( _test matrix matrix3 ) + ecbuild_add_test( + TARGET eckit_test_maths_${_test} + SOURCES test_${_test}.cc + LIBS eckit_maths eckit + ) +endforeach() + +ecbuild_add_test( + TARGET eckit_test_maths_convex_hull + SOURCES test_convex_hull.cc + CONDITION HAVE_CONVEX_HULL + LIBS eckit_maths eckit ) + diff --git a/tests/maths/test_convex_hull.cc b/tests/maths/test_convex_hull.cc new file mode 100644 index 000000000..f0ebe018d --- /dev/null +++ b/tests/maths/test_convex_hull.cc @@ -0,0 +1,338 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include +#include +#include +#include +#include + +#include "eckit/exception/Exceptions.h" +#include "eckit/maths/ConvexHull.h" +#include "eckit/maths/ConvexHullN.h" +#include "eckit/testing/Test.h" + + +//---------------------------------------------------------------------------------------------------------------------- + + +namespace eckit::test { + + +CASE("Qhull errors/exceptions") { + using maths::ConvexHull; + + + SECTION("input") { + for (size_t N : {2, 3, 4}) { + ConvexHull::coord_t coord((N + 1) * N + 1, 1.); + ASSERT(coord.size() % N != 0); + EXPECT_THROWS_AS(maths::Qhull(N, coord, "Qt"), AssertionFailed); // 0 < N && coord.size() % N == 0 + } + } + + + SECTION("qhull") { + struct { + const int errorCode; + const std::string what; + const std::string command; + const size_t N; + const ConvexHull::coord_t coord; + } static const tests[] = { + {6050, "QH6050 qhull error: dimension 1 must be > 1", "Qt", 1, {1, 1}}, + {6154, "QH6154 Qhull precision error: Initial simplex is flat", "Qt", 2, {1, 1, 2, 2, 3, 3}}, + {6214, "QH6214 qhull input error: not enough points", "Qt", 2, {1, 1}}, + {6412, "QH6412 qhull input error (qh_initqhull_globals)", "Qt", 2, {}}, + {6421, "QH6421 qhull internal error (qh_maxsimplex)", "Qt", 2, {1, 1, 1, 1, 1, 1}}, + }; + + auto trigger = [](const auto& test) { maths::Qhull(test.N, test.coord, test.command); }; + + for (const auto& test : tests) { + try { + trigger(test); + EXPECT(false); + } + catch (const ConvexHull::Exception& e) { + EXPECT_EQUAL(test.errorCode, e.errorCode); + EXPECT_EQUAL(test.what, std::string(e.what(), test.what.length())); + } + catch (...) { + EXPECT(false); + } + } + + EXPECT_THROWS_AS(trigger(tests[0]), ConvexHull::DimensionError); + EXPECT_THROWS_AS(trigger(tests[1]), ConvexHull::PrecisionError); + EXPECT_THROWS_AS(trigger(tests[2]), ConvexHull::InputError); + EXPECT_THROWS_AS(trigger(tests[3]), ConvexHull::InputError); + + // tests[4] does not throw a specialised error type + try { + trigger(tests[4]); + EXPECT(false); + } + catch (const ConvexHull::DimensionError&) { + EXPECT(false); + } + catch (const ConvexHull::InputError&) { + EXPECT(false); + } + catch (const ConvexHull::OptionError&) { + EXPECT(false); + } + catch (const ConvexHull::PrecisionError&) { + EXPECT(false); + } + catch (const ConvexHull::TopologyError&) { + EXPECT(false); + } + catch (const ConvexHull::Exception&) { + } + catch (...) { + EXPECT(false); + } + } +} + + +CASE("ConvexHullN, N=2") { + // Build convex hull + maths::ConvexHullN ch(std::vector>{ + {1, 2}, + {3, 1}, + {4, 4}, + {6, 5}, + {7, 0}, + {2, 5}, + {5, 7}, + {8, 3}, + {6, 9}, + {9, 6}, + }); + + // Inner points to convex hull + const std::set inner{2, 3, 6, 7 /*on edge*/}; + + SECTION("vertices and facets") { + const auto vertices = ch.list_vertices(); + EXPECT(vertices.size() == 6); + + const auto facets = ch.list_facets(); + EXPECT(facets.size() == 6); + + for (size_t vertex = 0; vertex < 10; ++vertex) { + auto inside = inner.find(vertex) != inner.end(); + auto count_in_vertices = std::count(vertices.begin(), vertices.end(), vertex); + auto count_in_facets = std::accumulate(facets.begin(), facets.end(), static_cast(0), + [vertex](auto c, const auto& facet) { + auto f = std::count(facet.begin(), facet.end(), vertex); + EXPECT(f <= 1); + return c + f; + }); + EXPECT_EQUAL(count_in_vertices, (inside ? 0 : 1)); + EXPECT_EQUAL(count_in_facets, (inside ? 0 : 2)); + } + } + + + SECTION("factory") { + // same as above + std::unique_ptr ch2(new maths::ConvexHullN<2>(std::vector>{ + {1, 2}, + {3, 1}, + {4, 4}, + {6, 5}, + {7, 0}, + {2, 5}, + {5, 7}, + {8, 3}, + {6, 9}, + {9, 6}, + })); + + auto vertex_set = [](const std::vector& list) { return std::set{list.cbegin(), list.cend()}; }; + EXPECT(vertex_set(ch.list_vertices()) == vertex_set(ch2->list_vertices())); + + // wrong dimensions + EXPECT_THROWS_AS(new maths::ConvexHullN<3>(std::vector>{ + {1, 2}, + {3, 1}, + {4, 4}, + }), + AssertionFailed); + } +} + + +CASE("ConvexHullN, N=3 (tetrahedron)") { + // Hull vertices 0-3 + maths::ConvexHullN ch(std::vector>{ + {0, 0, 1}, + {1, 0, -1}, + {-1, 1, -1}, + {-1, -1, -1}, + {0, 0, 0}, + }); + + + SECTION("vertices") { + const auto vertices = ch.list_vertices(); + EXPECT(vertices.size() == 4); + + for (size_t vertex = 0; vertex < 4; ++vertex) { + EXPECT(std::find(vertices.begin(), vertices.end(), vertex) != vertices.end()); + } + + EXPECT(std::find(vertices.begin(), vertices.end(), 4) == vertices.end()); + } + + + SECTION("facets") { + const auto count = ch.facets_n(); + decltype(count) correct{{3, 4}}; + EXPECT(count == correct); + + auto tri = ch.facets(3); + EXPECT(tri.size() == 4 * 3); + + const auto facets = ch.list_facets(); + for (const auto& fr : { + std::set{0, 1, 2}, + {0, 1, 3}, + {0, 2, 3}, + {1, 2, 3}, + }) { + EXPECT(1 == std::count_if(facets.begin(), facets.end(), [&fr](const auto& facet) { + ASSERT(facet.size() == 3); + return std::count(fr.begin(), fr.end(), facet[0]) == 1 + && std::count(fr.begin(), fr.end(), facet[1]) == 1 + && std::count(fr.begin(), fr.end(), facet[2]) == 1; + })); + + auto count = 0; + for (auto t = tri.begin(); t != tri.end(); t += 3) { + count += std::set(t, t + 3) == fr ? 1 : 0; + } + EXPECT_EQUAL(1, count); + } + } +} + + +CASE("ConvexHullN, N=3 (square pyramid)") { + // Hull vertices 1-5 + maths::ConvexHullN ch( + std::vector>{ + {0, 0, 0.1}, + {0, 0, 1}, + {1, 0, 0}, + {0, 1, 0}, + {-1, 0, 0}, + {0, -1, 0}, + {0, 0, 0}, + {0.1, 0.1, 0}, + }, + "Q"); + + + SECTION("vertices") { + const auto vertices = ch.list_vertices(); + + const std::set result(vertices.begin(), vertices.end()); + decltype(result) correct{1, 2, 3, 4, 5}; + EXPECT(result == correct); + } + + + SECTION("facets") { + // test facets_n(), facets(size_t) + const std::vector> correct{{1, 2, 3}, {1, 3, 4}, {1, 4, 5}, {1, 5, 2}, {2, 3, 4, 5}}; + + auto counts = ch.facets_n(); + EXPECT(4 == counts[3]); + EXPECT(1 == counts[4]); + EXPECT(5 == std::accumulate(counts.begin(), counts.end(), 0, [](size_t sum, const auto& count) { + return sum + count.second; + })); + + counts[5] = 0; // test non-existant 5-vertex facets + + for (const auto [Nv, Nf] : counts) { + const auto facets = ch.facets(Nv); + EXPECT(facets.size() == Nv * Nf); + + auto df = static_cast(Nv); + + for (auto f = facets.begin(); f != facets.end(); f += df) { + const std::set result(f, f + df); + EXPECT(1 == std::count_if(correct.begin(), correct.end(), [&result](const auto& correct) { + return result == correct; + })); + } + } + + + // test list_facets() + const auto facets = ch.list_facets(); + EXPECT(facets.size() == correct.size()); + + for (const auto& facet : facets) { + const std::set result(facet.begin(), facet.end()); + EXPECT(1 == std::count_if(correct.begin(), correct.end(), [&result](const auto& correct) { + return result == correct; + })); + } + } +} + + +CASE("Triangulation, N=3") { + auto tri = maths::Qhull(3, + { + 0.1, 0.1, 0.1, // + 0, 0, 0, // + 1, 0, 0, // + 0, 1, 0, // + 0, 0, 1, // + }, + "Qt") + .facets(3); + + EXPECT_EQUAL(tri.size(), 4 * 3); + + auto find_triangle = [&tri](const std::set& fr) { + auto count = 0; + for (auto t = tri.begin(); t != tri.end(); t += 3) { + count += std::set(t, t + 3) == fr ? 1 : 0; + } + return 1 == count; + }; + + EXPECT(find_triangle({1, 2, 3})); + EXPECT(find_triangle({1, 2, 4})); + EXPECT(find_triangle({1, 3, 4})); + EXPECT(find_triangle({2, 3, 4})); +} + + +} // namespace eckit::test + + +//---------------------------------------------------------------------------------------------------------------------- + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/maths/test_matrix.cc b/tests/maths/test_matrix.cc index 5ab483e0c..8b31da2f8 100644 --- a/tests/maths/test_matrix.cc +++ b/tests/maths/test_matrix.cc @@ -15,7 +15,6 @@ #include "eckit/testing/Test.h" -using namespace eckit::testing; using eckit::types::is_approximately_equal; namespace eckit::test { @@ -299,7 +298,7 @@ CASE("Mappings to existing data") { // y = A * x { std::array vector_x{1, 2}; - std::array vector_y; + std::array vector_y{}; auto x = ColVector::ConstMapType(vector_x.data(), 2); EXPECT_EQUAL(x(0), 1.); @@ -315,7 +314,7 @@ CASE("Mappings to existing data") { // y = x * A { std::array vector_x{1, 2, 3}; - std::array vector_y; + std::array vector_y{}; auto x = RowVector::ConstMapType(vector_x.data(), 3); EXPECT_EQUAL(x(0), 1.); @@ -335,5 +334,5 @@ CASE("Mappings to existing data") { } // namespace eckit::test int main(int argc, char** argv) { - return run_tests(argc, argv); + return eckit::testing::run_tests(argc, argv); } diff --git a/tests/maths/test_matrix3.cc b/tests/maths/test_matrix3.cc new file mode 100644 index 000000000..51e988b33 --- /dev/null +++ b/tests/maths/test_matrix3.cc @@ -0,0 +1,102 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include + +#include "eckit/maths/Matrix3.h" +#include "eckit/testing/Test.h" +#include "eckit/types/FloatCompare.h" + + +namespace eckit::test { + + +using Matrix = maths::Matrix3; + +constexpr double tolerance = 1.e-8; + + +//---------------------------------------------------------------------------------------------------------------------- + + +CASE("test determinant") { + Matrix M{1, + 2, + 3, // + 4, + 5, + 6, // + 7, + 8, + 1}; + + EXPECT(types::is_approximately_equal(M.determinant(), 24., tolerance)); +} + + +CASE("test inverse") { + auto is_approximately_equal = [](const Matrix& A, const Matrix& B) { + ASSERT(A.size() == B.size()); + return std::equal(A.begin(), A.end(), B.begin(), [](double a, double b) { + return types::is_approximately_equal(a, b, tolerance); + }); + }; + + Matrix M{1, + 2, + 3, // + 4, + 5, + 6, // + 7, + 8, + 1}; + + // Calculate inverse + auto W = M.inverse(); + EXPECT(is_approximately_equal(W, + {-43. / 24., + 22. / 24., + -3. / 24., // + 38. / 24., + -20. / 24., + 6. / 24., // + -3. / 24., + 6. / 24., + -3. / 24.})); + + // Calculate identity + auto I = Matrix::identity(); + EXPECT(is_approximately_equal(I, + {1, + 0, + 0, // + 0, + 1, + 0, // + 0, + 0, + 1})); + + EXPECT(is_approximately_equal(I, W * M)); + EXPECT(is_approximately_equal(I, M * W)); +} + + +//---------------------------------------------------------------------------------------------------------------------- + + +} // namespace eckit::test + + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +} diff --git a/tests/memory/test_factory.cc b/tests/memory/test_factory.cc index 11d88e76f..69f435002 100644 --- a/tests/memory/test_factory.cc +++ b/tests/memory/test_factory.cc @@ -8,40 +8,29 @@ * does it submit to any jurisdiction. */ -#include + #include +#include -#include "eckit/log/Log.h" #include "eckit/memory/Builder.h" +#include "eckit/memory/Factory.h" #include "eckit/memory/Owned.h" -#include "eckit/runtime/Tool.h" -#include "eckit/utils/Translator.h" -#include "eckit/value/Properties.h" - - #include "eckit/testing/Test.h" +#include "eckit/value/Properties.h" -using namespace std; -using namespace eckit; -using namespace eckit::testing; namespace eckit::test { -/// These tests are similar to the test for boost scoped_ptr and shared ptrs -/// This allows as in the future to drop out, our own home grown managed -/// ptr's in favour of the standards. - //---------------------------------------------------------------------------------------------------------------------- class Base0 { public: - typedef BuilderT0 builder_t; - - typedef std::shared_ptr Ptr; + using builder_t = BuilderT0; static std::string className() { return "eckit_test.Base0"; } - virtual ~Base0() {} + virtual ~Base0() = default; + virtual std::string foo() const = 0; }; @@ -49,10 +38,9 @@ class A0 : public Base0 { public: static std::string className() { return "eckit_test.A0"; } - A0() : - s1_("A.0") {} + A0() : s1_("A.0") {} - virtual std::string foo() const { return className() + "." + s1_; } + std::string foo() const override { return className() + "." + s1_; } private: std::string s1_; @@ -62,30 +50,28 @@ class B0 : public Base0 { public: static std::string className() { return "eckit_test.B0"; } - B0() : - s2_("B.0") {} + B0() : s2_("B.0") {} - virtual std::string foo() const { return className() + "." + s2_; } + std::string foo() const override { return className() + "." + s2_; } private: std::string s2_; }; -ConcreteBuilderT0 builder_A0; -ConcreteBuilderT0 builder_B0; +static const ConcreteBuilderT0 builder_A0; +static const ConcreteBuilderT0 builder_B0; //---------------------------------------------------------------------------------------------------------------------- class Base1 : public Owned { public: - typedef BuilderT1 builder_t; - typedef const Params& ARG1; - - typedef std::shared_ptr Ptr; + using builder_t = BuilderT1; + using ARG1 = const Properties&; static std::string className() { return "eckit_test.Base1"; } - virtual ~Base1() {} + virtual ~Base1() = default; + virtual std::string foo() const = 0; }; @@ -93,10 +79,9 @@ class A1 : public Base1 { public: static std::string className() { return "eckit_test.A1"; } - A1(const Params& p) : - s1_(p["mystr"].as() + ".1") {} + explicit A1(const Properties& p) : s1_(p["mystr"].as() + ".1") {} - virtual std::string foo() const { return className() + "." + s1_; } + std::string foo() const override { return className() + "." + s1_; } private: std::string s1_; @@ -106,31 +91,29 @@ class B1 : public Base1 { public: static std::string className() { return "eckit_test.B1"; } - B1(const Params& p) : - s2_(p["mystr"].as() + ".2") {} + explicit B1(const Properties& p) : s2_(p["mystr"].as() + ".2") {} - virtual std::string foo() const { return className() + "." + s2_; } + std::string foo() const override { return className() + "." + s2_; } private: std::string s2_; }; -ConcreteBuilderT1 builder_A1; -ConcreteBuilderT1 builder_B1; +static const ConcreteBuilderT1 builder_A1; +static const ConcreteBuilderT1 builder_B1; //---------------------------------------------------------------------------------------------------------------------- class Base2 : public Owned { public: - typedef BuilderT2 builder_t; - typedef std::string ARG1; - typedef int ARG2; - - typedef std::shared_ptr Ptr; + using builder_t = BuilderT2; + using ARG1 = std::string; + using ARG2 = int; static std::string className() { return "eckit_test.Base2"; } - virtual ~Base2() {} + virtual ~Base2() = default; + virtual std::string foo() const = 0; }; @@ -138,10 +121,9 @@ class A2 : public Base2 { public: static std::string className() { return "eckit_test.A2"; } - A2(std::string s, int i) : - s1_(s + "." + Translator()(i)) {} + A2(std::string s, int i) : s1_(s + "." + std::to_string(i)) {} - virtual std::string foo() const { return className() + "." + s1_; } + std::string foo() const override { return className() + "." + s1_; } private: std::string s1_; @@ -151,35 +133,34 @@ class B2 : public Base2 { public: static std::string className() { return "eckit_test.B2"; } - B2(std::string s, int i) : - s2_(s + "." + Translator()(i)) {} + B2(std::string s, int i) : s2_(s + "." + std::to_string(i)) {} - virtual std::string foo() const { return className() + "." + s2_; } + std::string foo() const override { return className() + "." + s2_; } private: std::string s2_; }; -ConcreteBuilderT2 builder_A2; -ConcreteBuilderT2 builder_B2; +static const ConcreteBuilderT2 builder_A2; +static const ConcreteBuilderT2 builder_B2_1; +static const ConcreteBuilderT2 builder_B2_2("eckit_test.B2.x"); //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_memory_factory_0") { - // std::cout << Factory::instance() << std::endl; - EXPECT(Factory::build_type() == "eckit_test.Base0"); + const auto& factory = Factory::instance(); - EXPECT(Factory::instance().size() == 2); + EXPECT(factory.size() == 2); - EXPECT(Factory::instance().exists("eckit_test.A0")); - EXPECT(Factory::instance().exists("eckit_test.B0")); + EXPECT(factory.exists("eckit_test.A0")); + EXPECT(factory.exists("eckit_test.B0")); - EXPECT(Factory::instance().get("eckit_test.A0").name() == "eckit_test.A0"); - EXPECT(Factory::instance().get("eckit_test.B0").name() == "eckit_test.B0"); + EXPECT(factory.get("eckit_test.A0").name() == "eckit_test.A0"); + EXPECT(factory.get("eckit_test.B0").name() == "eckit_test.B0"); - Base0::Ptr p1(Factory::instance().get("eckit_test.A0").create()); - Base0::Ptr p2(Factory::instance().get("eckit_test.B0").create()); + std::unique_ptr p1(factory.get("eckit_test.A0").create()); + std::unique_ptr p2(factory.get("eckit_test.B0").create()); EXPECT(p1); EXPECT(p2); @@ -189,23 +170,22 @@ CASE("test_eckit_memory_factory_0") { } CASE("test_eckit_memory_factory_1") { - // std::cout << Factory::instance() << std::endl; - EXPECT(Factory::build_type() == "eckit_test.Base1"); + const auto& factory = Factory::instance(); - EXPECT(Factory::instance().size() == 2); + EXPECT(factory.size() == 2); - EXPECT(Factory::instance().exists("eckit_test.A1")); - EXPECT(Factory::instance().exists("eckit_test.B1")); + EXPECT(factory.exists("eckit_test.A1")); + EXPECT(factory.exists("eckit_test.B1")); - EXPECT(Factory::instance().get("eckit_test.A1").name() == "eckit_test.A1"); - EXPECT(Factory::instance().get("eckit_test.B1").name() == "eckit_test.B1"); + EXPECT(factory.get("eckit_test.A1").name() == "eckit_test.A1"); + EXPECT(factory.get("eckit_test.B1").name() == "eckit_test.B1"); Properties p; p.set("mystr", "lolo"); - Base1::Ptr p1(Factory::instance().get("eckit_test.A1").create(Params(p))); - Base1::Ptr p2(Factory::instance().get("eckit_test.B1").create(Params(p))); + std::unique_ptr p1(factory.get("eckit_test.A1").create(p)); + std::unique_ptr p2(factory.get("eckit_test.B1").create(p)); EXPECT(p1); EXPECT(p2); @@ -215,28 +195,30 @@ CASE("test_eckit_memory_factory_1") { } CASE("test_eckit_memory_factory_2") { - // std::cout << Factory::instance() << std::endl; - EXPECT(Factory::build_type() == "eckit_test.Base2"); + const auto& factory = Factory::instance(); - EXPECT(Factory::instance().size() == 2); + EXPECT(factory.size() == 3); - EXPECT(Factory::instance().exists("eckit_test.A2")); - EXPECT(Factory::instance().exists("eckit_test.B2")); + EXPECT(factory.exists("eckit_test.A2")); + EXPECT(factory.exists("eckit_test.B2")); - EXPECT(Factory::instance().get("eckit_test.A2").name() == "eckit_test.A2"); - EXPECT(Factory::instance().get("eckit_test.B2").name() == "eckit_test.B2"); + EXPECT(factory.get("eckit_test.A2").name() == "eckit_test.A2"); + EXPECT(factory.get("eckit_test.B2").name() == "eckit_test.B2"); - string s("lolo"); + std::string s("lolo"); - Base2::Ptr p1(Factory::instance().get("eckit_test.A2").create(s, 42)); - Base2::Ptr p2(Factory::instance().get("eckit_test.B2").create(s, 42)); + std::unique_ptr p1(factory.get("eckit_test.A2").create(s, 42)); + std::unique_ptr p2(factory.get("eckit_test.B2").create(s, 42)); + std::unique_ptr p3(factory.get("eckit_test.B2.x").create(s, -42)); EXPECT(p1); EXPECT(p2); + EXPECT(p3); EXPECT(p1->foo() == "eckit_test.A2.lolo.42"); EXPECT(p2->foo() == "eckit_test.B2.lolo.42"); + EXPECT(p3->foo() == "eckit_test.B2.lolo.-42"); } //---------------------------------------------------------------------------------------------------------------------- @@ -244,5 +226,5 @@ CASE("test_eckit_memory_factory_2") { } // namespace eckit::test int main(int argc, char** argv) { - return run_tests(argc, argv); + return eckit::testing::run_tests(argc, argv); } diff --git a/tests/memory/test_memory_mmap.cc b/tests/memory/test_memory_mmap.cc index a470e752c..c94ef4641 100644 --- a/tests/memory/test_memory_mmap.cc +++ b/tests/memory/test_memory_mmap.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include #include #include diff --git a/tests/mpi/eckit_test_mpi.cc b/tests/mpi/eckit_test_mpi.cc index cb900e2d6..7469316ad 100644 --- a/tests/mpi/eckit_test_mpi.cc +++ b/tests/mpi/eckit_test_mpi.cc @@ -942,6 +942,9 @@ CASE("test_probe") { comm.barrier(); + std::stringstream errstr; + bool error = false; + auto count = nproc; while (count > 0) { auto status = comm.probe(comm.anySource(), comm.anyTag()); @@ -952,11 +955,20 @@ CASE("test_probe") { EXPECT(sz == 1); comm.receive(&data[status.source()], sz, status.source(), status.tag()); + errstr << "[test_probe:" << irank << "] receive data["< sts = comm.waitAll(requests); @@ -964,6 +976,9 @@ CASE("test_probe") { for (auto& st : sts) { EXPECT(st.error() == 0); } + + comm.barrier(); + EXPECT(!error); } CASE("test_iProbe") { @@ -982,6 +997,9 @@ CASE("test_iProbe") { comm.barrier(); + std::stringstream errstr; + bool error = false; + auto count = nproc; while (count > 0) { auto status = comm.iProbe(comm.anySource(), comm.anyTag()); @@ -996,12 +1014,20 @@ CASE("test_iProbe") { EXPECT(sz == 1); comm.receive(&data[status.source()], sz, status.source(), status.tag()); - + errstr << "[test_iProbe:" << irank << "] receive data["< sts = comm.waitAll(requests); @@ -1009,6 +1035,9 @@ CASE("test_iProbe") { for (auto& st : sts) { EXPECT(st.error() == 0); } + + comm.barrier(); + EXPECT(!error); } //---------------------------------------------------------------------------------------------------------------------- diff --git a/tests/option/eckit_test_option_cmdargs.cc b/tests/option/eckit_test_option_cmdargs.cc index 55a734f8b..e381a9615 100644 --- a/tests/option/eckit_test_option_cmdargs.cc +++ b/tests/option/eckit_test_option_cmdargs.cc @@ -1011,6 +1011,16 @@ CASE("test_eckit_option_cmdargs_value_with_equals") { #endif #if TESTCASE == 38 +// disable deprecated declarations warning here as we are testing it +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#elif defined(__INTEL_COMPILER) +#pragma warning ( push ) +#pragma warning ( disable:1786 ) +#elif defined(__NVCOMPILER) +#pragma diag_suppress 1445 +#endif CASE("test_eckit_option__allows_to_set_default_value_for_options") { options_t options; { @@ -1057,6 +1067,11 @@ CASE("test_eckit_option__allows_to_set_default_value_for_options") { EXPECT_EQUAL(v[3], 4L); } } +#ifdef __clang__ +#pragma clang diagnostic pop +#elif defined(__INTEL_COMPILER) +#pragma warning ( pop ) +#endif #endif #if TESTCASE == 39 diff --git a/tests/parser/test_yaml.cc b/tests/parser/test_yaml.cc index 9164a462b..00f91c94c 100644 --- a/tests/parser/test_yaml.cc +++ b/tests/parser/test_yaml.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include "eckit/eckit_config.h" diff --git a/tests/types/test-double-compare-speed.cc b/tests/types/test-double-compare-speed.cc index 05ebf1cac..19cf6d00a 100644 --- a/tests/types/test-double-compare-speed.cc +++ b/tests/types/test-double-compare-speed.cc @@ -8,7 +8,7 @@ * does it submit to any jurisdiction. */ -#include +#include #include "eckit/log/BigNum.h" #include "eckit/log/Timer.h" diff --git a/tests/utils/CMakeLists.txt b/tests/utils/CMakeLists.txt index 2bf0338e6..079e5c770 100644 --- a/tests/utils/CMakeLists.txt +++ b/tests/utils/CMakeLists.txt @@ -66,3 +66,7 @@ ecbuild_add_test( TARGET eckit_test_rsync CONDITION HAVE_RSYNC SOURCES test_rsync.cc LIBS eckit rsync ) + +ecbuild_add_test( TARGET eckit_test_regex + SOURCES test_regex.cc + LIBS eckit ) diff --git a/tests/utils/test_optional.cc b/tests/utils/test_optional.cc index 3fcac6501..6d553893a 100644 --- a/tests/utils/test_optional.cc +++ b/tests/utils/test_optional.cc @@ -122,18 +122,26 @@ CASE("copy and move assign") { EXPECT(!opt3); // Self move-assignement +#ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wself-move" +#endif opt2 = std::move(opt2); +#ifdef __clang__ #pragma clang diagnostic pop +#endif EXPECT(opt2); EXPECT(opt2.value() == 2); // Self copy-assignement +#ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wself-assign-overloaded" +#endif opt2 = opt2; +#ifdef __clang__ #pragma clang diagnostic pop +#endif EXPECT(opt2); EXPECT(opt2.value() == 2); @@ -198,10 +206,14 @@ CASE("non-trivial object: std::string") { // Test move assign to self Optional optTmp4("tmp4"); +#ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wself-move" +#endif optTmp4 = std::move(optTmp4); +#ifdef __clang__ #pragma clang diagnostic pop +#endif EXPECT(optTmp4); EXPECT_EQUAL(optTmp4.value(), std::string("tmp4")); diff --git a/src/eckit/memory/Builder.cc b/tests/utils/test_regex.cc similarity index 57% rename from src/eckit/memory/Builder.cc rename to tests/utils/test_regex.cc index 990e2de58..cf2db3e39 100644 --- a/src/eckit/memory/Builder.cc +++ b/tests/utils/test_regex.cc @@ -8,14 +8,31 @@ * does it submit to any jurisdiction. */ -#include "eckit/memory/Builder.h" +#include "eckit/eckit.h" + +#include "eckit/exception/Exceptions.h" +#include "eckit/utils/Regex.h" + +#include "eckit/testing/Test.h" + +using namespace std; +using namespace eckit; +using namespace eckit::testing; + +namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- -namespace eckit { +CASE("Escape special regex chars in string") { + EXPECT(Regex::escape(".^$*+-?()[]{}\\|") == "\\.\\^\\$\\*\\+\\-\\?\\(\\)\\[\\]\\{\\}\\\\\\|"); +} + -Builder::~Builder() {} //---------------------------------------------------------------------------------------------------------------------- -} // namespace eckit +} // namespace eckit::test + +int main(int argc, char* argv[]) { + return run_tests(argc, argv); +} diff --git a/tests/value/CMakeLists.txt b/tests/value/CMakeLists.txt index bd7558e0e..ffb946b0f 100644 --- a/tests/value/CMakeLists.txt +++ b/tests/value/CMakeLists.txt @@ -29,7 +29,7 @@ foreach( valueType nil boolean integer double string valuemap valuelist date ord LIBS eckit ) endforeach() -if( CMAKE_CXX_COMPILER_ID MATCHES "Cray" ) +if( CMAKE_CXX_COMPILER_ID STREQUAL "Cray" ) # Disable warnings for test_value_integer due to following: # "Integer conversion resulted in a change of sign." set_source_files_properties(test_value_integer.cc PROPERTIES COMPILE_FLAGS "-hmsglevel_4" ) diff --git a/tests/value/test_value_integer.cc b/tests/value/test_value_integer.cc index 65c6c354a..a85510a2e 100644 --- a/tests/value/test_value_integer.cc +++ b/tests/value/test_value_integer.cc @@ -8,6 +8,12 @@ * does it submit to any jurisdiction. */ +// Disable warning change of sign which is triggered on purpose +#if defined(__INTEL_COMPILER) +#pragma warning ( disable:68 ) +#elif defined(__NVCOMPILER) +#pragma diag_suppress 68 +#endif #include "eckit/testing/Test.h" #include "eckit/types/FloatCompare.h"