diff --git a/.github/ci-config.yml b/.github/ci-config.yml index 52a3c0a16..c1c0c52bf 100644 --- a/.github/ci-config.yml +++ b/.github/ci-config.yml @@ -1,3 +1,4 @@ +cmake_options: -DENABLE_ECKIT_GEO=ON dependencies: | ecmwf/ecbuild dependency_branch: develop 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 de73cb552..3a281ae6f 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 @@ -169,6 +179,34 @@ if( eckit_HAVE_CONVEX_HULL ) 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 ) @@ -265,6 +303,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 ) @@ -296,9 +341,9 @@ 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 ) 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 2ed224638..8ef1c1d37 100644 --- a/src/eckit/CMakeLists.txt +++ b/src/eckit/CMakeLists.txt @@ -82,246 +82,243 @@ 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() 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 @@ -343,407 +340,404 @@ 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/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) @@ -761,50 +755,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 @@ -828,10 +821,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 @@ -873,43 +866,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 @@ -921,13 +911,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 $ @@ -965,11 +955,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() @@ -981,7 +971,11 @@ 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/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/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/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/memory/Builder.cc b/src/eckit/geo/Iterator.cc similarity index 54% rename from src/eckit/memory/Builder.cc rename to src/eckit/geo/Iterator.cc index 990e2de58..ae0400338 100644 --- a/src/eckit/memory/Builder.cc +++ b/src/eckit/geo/Iterator.cc @@ -3,19 +3,29 @@ * * 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/memory/Builder.h" -//---------------------------------------------------------------------------------------------------------------------- +#include "eckit/geo/Iterator.h" + +#include "eckit/exception/Exceptions.h" +#include "eckit/geo/spec/Custom.h" + + +namespace eckit::geo { + -namespace eckit { +spec::Custom* Iterator::spec() const { + auto* custom = new spec::Custom; + ASSERT(custom != nullptr); -Builder::~Builder() {} + fill_spec(*custom); + return custom; +} -//---------------------------------------------------------------------------------------------------------------------- -} // namespace eckit +} // 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/maths/CMakeLists.txt b/src/eckit/maths/CMakeLists.txt index c5c76d200..bc4cac86c 100644 --- a/src/eckit/maths/CMakeLists.txt +++ b/src/eckit/maths/CMakeLists.txt @@ -1,11 +1,13 @@ list(APPEND eckit_maths_private_libs "${LAPACK_LIBRARIES}" "${BLAS_LIBRARIES}") list(APPEND eckit_maths_sources Eigen.h - Lapack.h Lapack.cc + Lapack.h Matrix.h + Matrix3.h MatrixEigen.h - MatrixLapack.h) + MatrixLapack.h +) if(eckit_HAVE_CONVEX_HULL) list(APPEND eckit_maths_sources ConvexHull.h ConvexHullN.h Qhull.cc Qhull.h) @@ -13,15 +15,17 @@ if(eckit_HAVE_CONVEX_HULL) 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) + 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( HAVE_EIGEN ) +if(HAVE_EIGEN) # 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/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/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/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/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/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 e303f5426..0cd9674a7 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; @@ -457,5 +434,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/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/maths/CMakeLists.txt b/tests/maths/CMakeLists.txt index 6828ba708..90a0b4de3 100644 --- a/tests/maths/CMakeLists.txt +++ b/tests/maths/CMakeLists.txt @@ -1,16 +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 +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_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); }