diff --git a/Cargo.lock b/Cargo.lock index 6034325..64fb002 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,7 +16,7 @@ checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "const-random", - "getrandom", + "getrandom 0.2.15", "once_cell", "version_check", "zerocopy", @@ -52,6 +52,62 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + [[package]] name = "approx" version = "0.5.1" @@ -109,7 +165,7 @@ dependencies = [ "chrono", "chrono-tz", "half", - "hashbrown", + "hashbrown 0.15.2", "num", ] @@ -397,7 +453,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -432,6 +488,56 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "4.5.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap-verbosity-flag" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34c77f67047557f62582784fd7482884697731b2932c7d37ced54bce2312e1e2" +dependencies = [ + "clap", + "log", +] + +[[package]] +name = "clap_builder" +version = "4.5.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + [[package]] name = "cmake" version = "0.1.54" @@ -441,6 +547,12 @@ dependencies = [ "cc", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "const-random" version = "0.1.18" @@ -456,7 +568,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom", + "getrandom 0.2.15", "once_cell", "tiny-keccak", ] @@ -476,6 +588,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -547,6 +668,27 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "earcutr" version = "0.4.3" @@ -575,6 +717,29 @@ dependencies = [ "syn", ] +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -588,7 +753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -600,7 +765,7 @@ dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -623,6 +788,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float_eq" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a80e3145d8ad11ba0995949bbcf48b9df2be62772b3d351ef017dff6ecb853" + [[package]] name = "float_next_after" version = "1.0.0" @@ -635,6 +806,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "geo" version = "0.29.3" @@ -725,6 +902,24 @@ dependencies = [ "wkt 0.12.0", ] +[[package]] +name = "geodesy" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c1778eaef1f258c89d8d3f0fadb42ef70f4c714d237714fede0936285661c20" +dependencies = [ + "anyhow", + "clap", + "clap-verbosity-flag", + "dirs", + "env_logger", + "float_eq", + "log", + "thiserror 1.0.69", + "uuid", + "wasm-bindgen", +] + [[package]] name = "geographiclib-rs" version = "0.2.4" @@ -772,6 +967,18 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "glob" version = "0.3.2" @@ -786,6 +993,10 @@ dependencies = [ "bincode", "geo", "geoarrow", + "geodesy", + "lru", + "moka", + "num-traits", "numpy", "proj", "pyo3", @@ -822,7 +1033,18 @@ checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", ] [[package]] @@ -847,7 +1069,7 @@ version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -925,7 +1147,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -934,6 +1156,12 @@ version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.11.0" @@ -949,6 +1177,30 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -1112,7 +1364,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1144,12 +1396,30 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "lru" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" +dependencies = [ + "hashbrown 0.16.0", +] + [[package]] name = "matrixmultiply" version = "0.3.9" @@ -1190,6 +1460,24 @@ dependencies = [ "adler2", ] +[[package]] +name = "moka" +version = "0.12.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "rustc_version", + "smallvec", + "tagptr", + "uuid", +] + [[package]] name = "ndarray" version = "0.16.1" @@ -1339,6 +1627,41 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + [[package]] name = "parse-zoneinfo" version = "0.3.1" @@ -1581,6 +1904,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -1631,6 +1960,17 @@ dependencies = [ "bitflags 2.9.0", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "regex" version = "1.11.1" @@ -1709,7 +2049,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1722,7 +2062,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.3", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1737,6 +2077,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "scroll" version = "0.11.0" @@ -1821,7 +2167,7 @@ version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ece03ff43cd2a9b57ebf776ea5e78bd30b3b4185a619f041079f4109f385034" dependencies = [ - "hashbrown", + "hashbrown 0.15.2", "num-traits", "robust", "smallvec", @@ -1839,6 +2185,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.100" @@ -1850,6 +2202,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tar" version = "0.4.44" @@ -1979,6 +2337,23 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "version_check" version = "0.9.5" @@ -1991,6 +2366,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -2069,7 +2453,7 @@ checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.1.1", "windows-result", "windows-strings", ] @@ -2102,13 +2486,19 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-result" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -2117,7 +2507,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" dependencies = [ - "windows-link", + "windows-link 0.1.1", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] @@ -2126,7 +2525,31 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -2135,64 +2558,171 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.7.4" @@ -2202,6 +2732,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + [[package]] name = "wkb" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index d4e9aa1..f2f0999 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,7 @@ pyo3-arrow = "0.8" arrow = "54.3" rstar = { version = "0.12", features = ["serde"] } serde = "1.0" +num-traits = "0.2.19" +geodesy = "0.14.0" +lru = "0.16.2" +moka = { version = "0.12.11", features = ["sync"] } diff --git a/src/lib.rs b/src/lib.rs index 51933a6..d7fffc0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ use pyo3::prelude::*; mod index; mod python; mod rtreeobject; +mod spherical; mod trait_; use self::python::{create_empty, RTree}; diff --git a/src/spherical/envelope.rs b/src/spherical/envelope.rs new file mode 100644 index 0000000..b4d4ed7 --- /dev/null +++ b/src/spherical/envelope.rs @@ -0,0 +1,346 @@ +use crate::spherical::point::{max_inline, SphericalPoint}; +use num_traits::{Bounded, One, Zero}; +use rstar::{Envelope, Point, RTreeObject}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, PartialOrd, Deserialize, Serialize)] +struct SphericalAABB { + lower: SphericalPoint, + upper: SphericalPoint, +} + +impl SphericalAABB { + /// Returns the spherical AABB encompassing a single point. + pub fn from_point(p: SphericalPoint) -> Self { + Self { + lower: p.clone(), + upper: p, + } + } + + /// Returns the spherical AABB's lower corner. + /// + /// This is the point contained within the AABB with the smallest coordinate value in each + /// dimension. + pub fn lower(&self) -> SphericalPoint { + self.lower.clone() + } + + /// Returns the spherical AABB's upper corner. + /// + /// This is the point contained within the AABB with the largest coordinate value in each + /// dimension. + pub fn upper(&self) -> SphericalPoint { + self.upper.clone() + } + + /// Creates a new spherical AABB encompassing two points. + pub fn from_corners(p1: SphericalPoint, p2: SphericalPoint) -> Self { + Self { + lower: p1.min_point(&p2), + upper: p1.max_point(&p2), + } + } + + /// Creates a new AABB encompassing a collection of points. + pub fn from_points<'a, I>(i: I) -> Self + where + I: IntoIterator + 'a, + { + i.into_iter().fold( + Self { + lower: SphericalPoint::from_value(::Scalar::max_value()), + upper: SphericalPoint::from_value(::Scalar::min_value()), + }, + |aabb, p| Self { + lower: aabb.lower.min_point(p), + upper: aabb.upper.max_point(p), + }, + ) + } + + /// Returns the point within this AABB closest to a given point. + /// + /// If `point` is contained within the AABB, `point` will be returned. + pub fn min_point(&self, point: &SphericalPoint) -> SphericalPoint { + self.upper.min_point(&self.lower.max_point(point)) + } + + /// Returns the squared distance to the spherical AABB's [min_point](SphericalAABB::min_point) + pub fn distance_2(&self, point: &SphericalPoint) -> ::Scalar { + if self.contains_point(point) { + Zero::zero() + } else { + self.min_point(point).distance_2(point) + } + } +} + +impl Envelope for SphericalAABB { + type Point = SphericalPoint; + + fn new_empty() -> Self { + let max = ::Scalar::max_value(); + let min = ::Scalar::min_value(); + Self { + lower: SphericalPoint::from_value(max), + upper: SphericalPoint::from_value(min), + } + } + + fn contains_point(&self, point: &SphericalPoint) -> bool { + self.min_point(point) + .all_component_wise(point, |l, r| l == r) + } + + fn contains_envelope(&self, other: &Self) -> bool { + // other is contained if both corner points are contained + self.contains_point(&other.lower) && self.contains_point(other.upper) + } + + fn merge(&mut self, other: &Self) { + self.lower = self.lower.min_point(&other.lower); + self.upper = self.upper.max_point(&other.upper); + } + + fn merged(&self, other: &Self) -> Self { + SphericalAABB { + lower: self.lower.min_point(&other.lower), + upper: self.upper.max_point(&other.upper), + } + } + + fn intersects(&self, other: &Self) -> bool { + self.lower.all_component_wise(&other.upper, |l, r| l <= r) + && self.upper.all_component_wise(&other.lower, |l, r| l >= r) + } + + fn area(&self) -> ::Scalar { + let zero = ::Scalar::zero(); + let one = ::Scalar::one(); + let diag = self.upper.sub(&self.lower); + diag.fold(one, |acc, cur| max_inline(cur, zero) * acc) + } + + fn distance_2(&self, point: &SphericalPoint) -> ::Scalar { + self.min_point(point).distance_2(point) + } + + fn min_max_dist_2(&self, point: &SphericalPoint) -> ::Scalar { + let l = self.lower.sub(point); + let u = self.upper.sub(point); + let mut max_diff = (Zero::zero(), Zero::zero(), 0); // diff, min, index + let mut result = SphericalPoint::new(); + + for i in 0..SphericalPoint::DIMENSIONS { + let mut min = l.nth(i); + let mut max = u.nth(i); + max = max * max; + min = min * min; + if max < min { + core::mem::swap(&mut min, &mut max); + } + + let diff = max - min; + *result.nth_mut(i) = max; + + if diff >= max_diff.0 { + max_diff = (diff, min, i); + } + } + + *result.nth_mut(max_diff.2) = max_diff.1; + result.fold(Zero::zero(), |acc, curr| acc + curr) + } + + fn center(&self) -> Self::Point { + let one = ::Scalar::one(); + let two = one + one; + self.lower.component_wise(&self.upper, |x, y| (x + y) / two) + } + + fn intersection_area(&self, other: &Self) -> ::Scalar { + SphericalAABB { + lower: self.lower.max_point(&other.lower), + upper: self.upper.min_point(&other.upper), + } + .area() + } + + fn perimeter_value(&self) -> ::Scalar { + let diag = self.upper.sub(&self.lower); + let zero = ::Scalar::zero(); + max_inline(diag.fold(zero, |acc, value| acc + value), zero) + } + + fn sort_envelopes>(axis: usize, envelopes: &mut [T]) { + envelopes.sort_unstable_by(|l, r| { + l.envelope() + .lower + .nth(axis) + .partial_cmp(&r.envelope().lower.nth(axis)) + .unwrap() + }); + } + + fn partition_envelopes>( + axis: usize, + envelopes: &mut [T], + selection_size: usize, + ) { + envelopes.select_nth_unstable_by(selection_size, |l, r| { + l.envelope() + .lower + .nth(axis) + .partial_cmp(&r.envelope().lower.nth(axis)) + .unwrap() + }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_from_point() { + let p = SphericalPoint::create(10.0, 10.0); + let actual = SphericalAABB::from_point(p); + + let expected = SphericalAABB { + lower: SphericalPoint::create(10.0, 10.0), + upper: SphericalPoint::create(10.0, 10.0), + }; + + assert_eq!(actual, expected); + } + + #[test] + fn test_lower() { + let aabb = SphericalAABB { + lower: SphericalPoint::create(0.0, 0.0), + upper: SphericalPoint::create(10.0, 10.0), + }; + + let actual = aabb.lower(); + let expected = SphericalPoint::create(0.0, 0.0); + + assert_eq!(actual, expected); + } + + #[test] + fn test_upper() { + let aabb = SphericalAABB { + lower: SphericalPoint::create(0.0, 0.0), + upper: SphericalPoint::create(10.0, 10.0), + }; + + let actual = aabb.upper(); + let expected = SphericalPoint::create(10.0, 10.0); + + assert_eq!(actual, expected); + } + + #[test] + fn test_from_corners() { + let p1 = SphericalPoint::create(10.0, 0.0); + let p2 = SphericalPoint::create(0.0, 10.0); + + let actual = SphericalAABB::from_corners(p1, p2); + let expected = SphericalAABB { + lower: SphericalPoint::create(0.0, 0.0), + upper: SphericalPoint::create(10.0, 10.0), + }; + + assert_eq!(actual, expected); + } + + #[test] + fn test_from_corners_central() { + let p1 = SphericalPoint::create(10.0, 10.0); + let p2 = SphericalPoint::create(350.0, -20.0); + + let actual = SphericalAABB::from_corners(p1, p2); + let expected = SphericalAABB { + lower: SphericalPoint::create(350.0, -20.0), + upper: SphericalPoint::create(10.0, 10.0), + }; + + assert_eq!(actual, expected); + } + + #[test] + fn test_from_points() { + let points = vec![ + SphericalPoint::create(0.0, 0.0), + SphericalPoint::create(5.0, 0.0), + SphericalPoint::create(10.0, 0.0), + SphericalPoint::create(0.0, 5.0), + SphericalPoint::create(5.0, 5.0), + SphericalPoint::create(10.0, 5.0), + SphericalPoint::create(0.0, 10.0), + SphericalPoint::create(5.0, 10.0), + SphericalPoint::create(10.0, 10.0), + ]; + + let actual = SphericalAABB::from_points(&points); + let expected = SphericalAABB { + lower: SphericalPoint::create(0.0, 0.0), + upper: SphericalPoint::create(10.0, 10.0), + }; + + assert_eq!(actual, expected); + } + + #[test] + fn test_min_point() { + let aabb = SphericalAABB { + lower: SphericalPoint::create(350.0, -20.0), + upper: SphericalPoint::create(10.0, 10.0), + }; + + let point = SphericalPoint::create(15.0, 0.0); + let expected = SphericalPoint::create(10.0, 0.0); + + let actual = aabb.min_point(&point); + + assert_eq!(actual, expected); + + let point = SphericalPoint::create(320.0, -40.0); + let expected = SphericalPoint::create(350.0, -20.0); + + let actual = aabb.min_point(&point); + + assert_eq!(actual, expected); + + let point = SphericalPoint::create(359.0, -10.0); + let expected = SphericalPoint::create(359.0, -10.0); + + let actual = aabb.min_point(&point); + + assert_eq!(actual, expected); + } + + #[test] + fn test_contains_point() { + let aabb = SphericalAABB { + lower: SphericalPoint::create(-10.0, -20.0), + upper: SphericalPoint::create(10.0, 10.0), + }; + + let point = SphericalPoint::create(-5.0, 5.0); + let actual = aabb.contains_point(&point); + + assert!(actual); + + let point = SphericalPoint::create(345.0, 5.0); + let actual = aabb.contains_point(&point); + + assert!(!actual); + + let point = SphericalPoint::create(5.0, -45.0); + let actual = aabb.contains_point(&point); + + assert!(!actual); + } +} diff --git a/src/spherical/mod.rs b/src/spherical/mod.rs new file mode 100644 index 0000000..54008aa --- /dev/null +++ b/src/spherical/mod.rs @@ -0,0 +1,2 @@ +mod envelope; +mod point; diff --git a/src/spherical/point.rs b/src/spherical/point.rs new file mode 100644 index 0000000..7483a3e --- /dev/null +++ b/src/spherical/point.rs @@ -0,0 +1,306 @@ +use geodesy::coord::Coor2D; +use geodesy::ellps::{Ellipsoid, Geodesics}; +use num_traits::Zero; +use rstar::{Point, RTreeNum}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, PartialEq, PartialOrd, Debug, Deserialize, Serialize)] +pub struct SphericalPoint { + lon: f64, + lat: f64, +} + +impl Point for SphericalPoint { + type Scalar = f64; + const DIMENSIONS: usize = 2; + + fn generate(mut generator: impl FnMut(usize) -> Self::Scalar) -> Self { + Self { + lon: generator(0).rem_euclid(360.0), + lat: generator(1), + } + } + + #[inline] + fn nth(&self, index: usize) -> Self::Scalar { + match index { + 0 => self.lon, + 1 => self.lat, + _ => unreachable!(), + } + } + + #[inline] + fn nth_mut(&mut self, index: usize) -> &mut Self::Scalar { + match index { + 0 => &mut self.lon, + 1 => &mut self.lat, + _ => unreachable!(), + } + } +} + +impl SphericalPoint { + /// Returns a new Point with all components set to zero. + pub fn new() -> Self { + Self::from_value(Zero::zero()) + } + + pub fn create(lon: f64, lat: f64) -> Self { + Self { + lon: lon.rem_euclid(360.0), + lat, + } + } + + /// Applies `f` to each pair of components of `self` and `other`. + pub fn component_wise( + &self, + other: &Self, + mut f: impl FnMut(::Scalar, ::Scalar) -> ::Scalar, + ) -> Self { + Self::generate(|i| f(self.nth(i), other.nth(i))) + } + + /// Returns whether all pairs of components of `self` and `other` pass test closure `f`. Short circuits if any result is false. + pub fn all_component_wise( + &self, + other: &Self, + mut f: impl FnMut(::Scalar, ::Scalar) -> bool, + ) -> bool { + (0..Self::DIMENSIONS).all(|i| f(self.nth(i), other.nth(i))) + } + + /// Returns the dot product of `self` and `rhs`. + pub fn dot(&self, rhs: &Self) -> ::Scalar { + self.component_wise(rhs, |l, r| l * r) + .fold(Zero::zero(), |acc, val| acc + val) + } + + /// Folds (aka reduces or injects) the Point component wise using `f` and returns the result. + /// fold() takes two arguments: an initial value, and a closure with two arguments: an 'accumulator', and the value of the current component. + /// The closure returns the value that the accumulator should have for the next iteration. + /// + /// The `start_value` is the value the accumulator will have on the first call of the closure. + /// + /// After applying the closure to every component of the Point, fold() returns the accumulator. + pub fn fold(&self, start_value: T, mut f: impl FnMut(T, ::Scalar) -> T) -> T { + (0..Self::DIMENSIONS).fold(start_value, |accumulated, i| f(accumulated, self.nth(i))) + } + + /// Returns a Point with every component set to `value`. + pub fn from_value(value: ::Scalar) -> Self { + Self::generate(|_| value) + } + + /// Returns the Point that is the south-west corner of the box defined by `self` and `other`. + pub fn min_point(&self, other: &Self) -> Self { + // For spherical coords: choose the westernmost longitude using the smaller + // angle between the two points. With that, we define l1 as west of l2 + // if the angle from l1 to l2 is less than 180 degree. + let min_lat = self.lat.min(other.lat); + let min_lon = if (other.lon - self.lon).rem_euclid(360.0) <= 180.0 { + self.lon + } else { + other.lon + }; + + Self::create(min_lon, min_lat) + } + + /// Returns the Point that is the north-east corner of the box defined by `self` and `other`. + pub fn max_point(&self, other: &Self) -> Self { + // For spherical coords: choose the easternmost longitude using the smaller + // angle between the two points. With that, we define l1 as east of l2 + // if the angle from l1 to l2 is greater than 180 degree. + let max_lat = self.lat.max(other.lat); + let max_lon = if (other.lon - self.lon).rem_euclid(360.0) > 180.0 { + self.lon + } else { + other.lon + }; + + Self::create(max_lon, max_lat) + } + + /// Returns the squared length of this Point as if it was a vector. + pub fn length_2(&self) -> ::Scalar { + self.fold(Zero::zero(), |acc, cur| cur * cur + acc) + } + + /// Subtracts `other` from `self` component wise. + pub fn sub(&self, other: &Self) -> Self { + self.component_wise(other, |l, r| l - r) + } + + /// Adds `other` to `self` component wise. + pub fn add(&self, other: &Self) -> Self { + self.component_wise(other, |l, r| l + r) + } + + /// Multiplies `self` with `scalar` component wise. + pub fn mul(&self, scalar: ::Scalar) -> Self { + self.map(|coordinate| coordinate * scalar) + } + + /// Applies `f` to `self` component wise. + pub fn map( + &self, + mut f: impl FnMut(::Scalar) -> ::Scalar, + ) -> Self { + Self::generate(|i| f(self.nth(i))) + } + + /// Returns the squared distance between `self` and `other`. + pub fn distance_2(&self, other: &Self) -> ::Scalar { + let ellipsoid = Ellipsoid::new(6371000.0, 1.0); + + let p1 = Coor2D::gis(self.lon, self.lat); + let p2 = Coor2D::gis(other.lon, other.lat); + + ellipsoid.distance(&p1, &p2).powi(2) + } +} + +#[inline] +pub fn min_inline(a: S, b: S) -> S +where + S: RTreeNum, +{ + if a < b { + a + } else { + b + } +} + +#[inline] +pub fn max_inline(a: S, b: S) -> S +where + S: RTreeNum, +{ + if a > b { + a + } else { + b + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new() { + let actual = SphericalPoint::new(); + + assert_eq!(actual.lon, 0f64); + assert_eq!(actual.lat, 0f64); + } + + #[test] + fn test_create() { + let actual = SphericalPoint::create(2.0, 6.0); + + assert_eq!(actual.lon, 2.0); + assert_eq!(actual.lat, 6.0); + } + + #[test] + fn test_component_wise() { + let p1 = SphericalPoint::create(10.0, 10.0); + let p2 = SphericalPoint::create(20.0, 5.0); + + let actual = p1.component_wise(&p2, |a, b| a - b); + let expected = SphericalPoint::create(-10.0, 5.0); + + assert_eq!(actual, expected); + } + + #[test] + fn test_all_component_wise() { + let p1 = SphericalPoint::create(10.0, 10.0); + let p2 = SphericalPoint::create(5.0, 15.0); + + let actual = p1.all_component_wise(&p2, |a, b| (a - b).abs() < 10.0); + + assert!(actual); + } + + #[test] + fn test_min_point() { + let p1 = SphericalPoint { + lon: 10.0, + lat: 0.0, + }; + let p2 = SphericalPoint { + lon: 0.0, + lat: 10.0, + }; + + let actual = p1.min_point(&p2); + let expected = SphericalPoint { lon: 0.0, lat: 0.0 }; + + assert_eq!(actual, expected); + } + + #[test] + fn test_min_point2() { + let p1 = SphericalPoint { + lon: 350.0, + lat: 0.0, + }; + let p2 = SphericalPoint { + lon: 0.0, + lat: 10.0, + }; + + let actual = p1.min_point(&p2); + let expected = SphericalPoint { + lon: 350.0, + lat: 0.0, + }; + + assert_eq!(actual, expected); + } + + #[test] + fn test_max_point1() { + let p1 = SphericalPoint { + lon: 10.0, + lat: 0.0, + }; + let p2 = SphericalPoint { + lon: 0.0, + lat: 10.0, + }; + + let actual = p1.max_point(&p2); + let expected = SphericalPoint { + lon: 10.0, + lat: 10.0, + }; + + assert_eq!(actual, expected); + } + + #[test] + fn test_max_point2() { + let p1 = SphericalPoint { + lon: 350.0, + lat: 0.0, + }; + let p2 = SphericalPoint { + lon: 0.0, + lat: 10.0, + }; + + let actual = p1.max_point(&p2); + let expected = SphericalPoint { + lon: 0.0, + lat: 10.0, + }; + + assert_eq!(actual, expected); + } +}