diff --git a/.gitignore b/.gitignore index 4fe000d..76b13c0 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ target/ # Added by cargo /target + +.idea/ diff --git a/Cargo.lock b/Cargo.lock index cdbd6b7..ad4d7ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" @@ -21,9 +21,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -42,9 +42,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "approx" @@ -57,24 +57,18 @@ dependencies = [ [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ "derive_arbitrary", ] [[package]] name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "2.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "block-buffer" @@ -87,15 +81,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.14.0" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] name = "byteorder" @@ -105,22 +99,20 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bzip2" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" dependencies = [ "bzip2-sys", - "libc", ] [[package]] name = "bzip2-sys" -version = "0.1.11+1.0.8" +version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ "cc", - "libc", "pkg-config", ] @@ -132,10 +124,11 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.6" +version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -143,15 +136,15 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "ciborium" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", @@ -160,15 +153,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", @@ -186,18 +179,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.6" +version = "4.5.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.4.6" +version = "4.5.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0" dependencies = [ "anstyle", "clap_lex", @@ -205,9 +198,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "constant_time_eq" @@ -217,18 +210,18 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] @@ -241,9 +234,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -286,26 +279,21 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] @@ -314,6 +302,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.6" @@ -326,24 +320,24 @@ dependencies = [ [[package]] name = "deflate64" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" +checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" [[package]] name = "deranged" -version = "0.3.11" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", ] [[package]] name = "derive_arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", @@ -384,31 +378,27 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "errno" -version = "0.3.5" +name = "find-msvc-tools" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" -dependencies = [ - "libc", - "windows-sys", -] +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "flate2" -version = "1.0.35" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" dependencies = [ "crc32fast", "miniz_oxide", @@ -428,9 +418,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -465,9 +455,10 @@ dependencies = [ "geo-traits", "geozero", "num-traits", + "rand", "rayon", "rstar", - "thiserror 1.0.49", + "thiserror 1.0.69", "tinyvec", "zip", ] @@ -496,23 +487,23 @@ dependencies = [ [[package]] name = "geographiclib-rs" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e5ed84f8089c70234b0a8e0aedb6dc733671612ddc0d37c6066052f9781960" +checksum = "f611040a2bb37eaa29a78a128d1e92a378a03e0b6e66ae27398d42b1ba9a7841" dependencies = [ "libm", ] [[package]] name = "geojson" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d728c1df1fbf328d74151efe6cb0586f79ee813346ea981add69bd22c9241b" +checksum = "e26f3c45b36fccc9cf2805e61d4da6bc4bbd5a3a9589b01afa3a40eff703bd79" dependencies = [ "log", "serde", "serde_json", - "thiserror 1.0.49", + "thiserror 2.0.17", ] [[package]] @@ -524,27 +515,47 @@ dependencies = [ "geo-types", "geojson", "log", + "scroll", "serde_json", - "thiserror 1.0.49", + "thiserror 1.0.69", "wkt", ] [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + [[package]] name = "half" -version = "1.8.2" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] [[package]] name = "hash32" @@ -557,15 +568,21 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + [[package]] name = "heapless" version = "0.8.0" @@ -578,9 +595,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hmac" @@ -608,9 +625,9 @@ checksum = "9190f86706ca38ac8add223b2aed8b1330002b5cdbbce28fb58b10914d38fc27" [[package]] name = "i_overlay" -version = "4.0.2" +version = "4.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c291f5c15a84f0e9126ff050719c5ca50227b27947b52526ee8370287dfc9e" +checksum = "0fcccbd4e4274e0f80697f5fbc6540fdac533cce02f2081b328e68629cce24f9" dependencies = [ "i_float", "i_key_sort", @@ -636,32 +653,32 @@ checksum = "35e6d558e6d4c7b82bc51d9c771e7a927862a161a7d87bf2b0541450e0e20915" [[package]] name = "indexmap" -version = "2.7.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.0", ] [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", - "rustix", - "windows-sys", + "libc", + "windows-sys 0.59.0", ] [[package]] @@ -684,57 +701,47 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.169" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "linux-raw-sys" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" - -[[package]] -name = "lockfree-object-pool" -version = "0.1.6" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "log" -version = "0.4.22" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lzma-rs" @@ -747,27 +754,30 @@ dependencies = [ ] [[package]] -name = "memchr" -version = "2.7.4" +name = "lzma-sys" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] [[package]] -name = "memoffset" -version = "0.9.0" +name = "memchr" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "miniz_oxide" -version = "0.8.2" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -778,9 +788,9 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -788,15 +798,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "pbkdf2" @@ -810,15 +820,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -829,15 +839,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] @@ -850,31 +860,37 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" 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" @@ -902,14 +918,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.16", ] [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -917,9 +933,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -927,9 +943,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaac441002f822bc9705a681810a4dd2963094b9ca0ddc41cb963a4c189189ea" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -939,9 +955,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.2" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5011c7e263a695dc8ca064cddb722af1be54e517a280b12a5356f98366899e5d" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -950,21 +966,21 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "robust" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf4a6aa5f6d6888f39e980649f3ad6b666acdce1d78e95b8a2cb076e687ae30" +checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" [[package]] name = "rstar" -version = "0.12.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133315eb94c7b1e8d0cb097e5a710d850263372fd028fff18969de708afc7008" +checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" dependencies = [ "heapless", "num-traits", @@ -972,23 +988,16 @@ dependencies = [ ] [[package]] -name = "rustix" -version = "0.38.19" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -1000,25 +1009,35 @@ dependencies = [ ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "scroll" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" [[package]] name = "serde" -version = "1.0.189" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.189" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1027,13 +1046,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", + "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -1067,11 +1088,11 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "spade" -version = "2.13.1" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ece03ff43cd2a9b57ebf776ea5e78bd30b3b4185a619f041079f4109f385034" +checksum = "fb313e1c8afee5b5647e00ee0fe6855e3d529eb863a0fdae1d60006c4d1e9990" dependencies = [ - "hashbrown", + "hashbrown 0.15.5", "num-traits", "robust", "smallvec", @@ -1079,9 +1100,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "subtle" @@ -1091,9 +1112,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.92" +version = "2.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ae51629bf965c5c098cc9e87908a3df5301051a9e087d6f9bef5c9771ed126" +checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b" dependencies = [ "proc-macro2", "quote", @@ -1102,27 +1123,27 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.49" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.49", + "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "2.0.9" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.9", + "thiserror-impl 2.0.17", ] [[package]] name = "thiserror-impl" -version = "1.0.49" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -1131,9 +1152,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.9" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -1142,9 +1163,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "num-conv", @@ -1155,9 +1176,9 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "tinytemplate" @@ -1171,9 +1192,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -1186,15 +1207,15 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "typenum" -version = "1.17.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" [[package]] name = "version_check" @@ -1204,9 +1225,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -1214,29 +1235,40 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[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.87" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -1245,9 +1277,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1255,9 +1287,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -1268,69 +1300,66 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "winapi" -version = "0.3.9" +name = "winapi-util" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "windows-sys 0.61.2", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "winapi-util" -version = "0.1.6" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "winapi", + "windows-targets", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets", + "windows-link", ] [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +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", @@ -1339,45 +1368,57 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "wkt" @@ -1388,24 +1429,32 @@ dependencies = [ "geo-types", "log", "num-traits", - "thiserror 1.0.49", + "thiserror 1.0.69", +] + +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", ] [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -1414,9 +1463,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] @@ -1434,9 +1483,9 @@ dependencies = [ [[package]] name = "zip" -version = "2.2.2" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" dependencies = [ "aes", "arbitrary", @@ -1447,15 +1496,16 @@ dependencies = [ "deflate64", "displaydoc", "flate2", + "getrandom 0.3.4", "hmac", "indexmap", "lzma-rs", "memchr", "pbkdf2", - "rand", "sha1", - "thiserror 2.0.9", + "thiserror 2.0.17", "time", + "xz2", "zeroize", "zopfli", "zstd", @@ -1463,41 +1513,39 @@ dependencies = [ [[package]] name = "zopfli" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" dependencies = [ "bumpalo", "crc32fast", - "lockfree-object-pool", "log", - "once_cell", "simd-adler32", ] [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index a2beb64..8c5c9c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,14 @@ categories = ["data-structures", "algorithms", "science::geo"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] +use-geo_0_31 = ["geo_0_31"] + [dependencies] bytemuck = "1" float_next_after = "1" +geo_0_31 = { package = "geo", version = "0.31", optional = true } geo-traits = "0.3" num-traits = "0.2" rayon = { version = "1.8.0", optional = true } @@ -23,11 +28,17 @@ tinyvec = { version = "1", features = ["alloc", "rustc_1_40"] } [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } -geo = "0.31.0" -geozero = "0.14" +geo_0_31 = { package = "geo", version = "0.31.0" } +geozero = { version = "0.14", features = ["with-wkb", "with-geo"] } rstar = "0.12" zip = "2.2.2" +rand = "0.8" [[bench]] name = "rtree" harness = false + +[[bench]] +name = "distance" +harness = false +required-features = ["use-geo_0_31"] diff --git a/benches/distance.rs b/benches/distance.rs new file mode 100644 index 0000000..8e3a8cc --- /dev/null +++ b/benches/distance.rs @@ -0,0 +1,228 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use geo_0_31::algorithm::{Distance, Euclidean}; +use geo_0_31::{coord, Geometry, LineString, Point, Polygon}; +use geo_index::rtree::distance::{ + DistanceMetric, EuclideanDistance, HaversineDistance, SliceGeometryAccessor, +}; +use geo_index::rtree::sort::HilbertSort; +use geo_index::rtree::{RTreeBuilder, RTreeIndex, SimpleDistanceMetric}; +use geo_index::IndexableNum; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; + +// Simple distance metric for benchmarks +struct SimpleMetric; + +impl SimpleDistanceMetric for SimpleMetric { + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { + let dx = x2 - x1; + let dy = y2 - y1; + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } + + fn distance_to_bbox(&self, x: N, y: N, min_x: N, min_y: N, max_x: N, max_y: N) -> N { + let dx = if x < min_x { + min_x - x + } else if x > max_x { + x - max_x + } else { + N::zero() + }; + let dy = if y < min_y { + min_y - y + } else if y > max_y { + y - max_y + } else { + N::zero() + }; + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } +} + +impl DistanceMetric for SimpleMetric { + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) + } +} + +fn generate_test_data(n: usize) -> (Vec>, Vec>) { + let mut rng = StdRng::seed_from_u64(42); + let mut points = Vec::with_capacity(n); + let mut geometries = Vec::with_capacity(n); + + for i in 0..n { + let x = rng.gen_range(-180.0..180.0); + let y = rng.gen_range(-90.0..90.0); + let point = Point::new(x, y); + points.push(point); + + // Create a mix of geometry types + let geom = match i % 3 { + 0 => Geometry::Point(point), + 1 => { + // Create a small line around the point + let offset = 0.01; + Geometry::LineString(LineString::new(vec![ + coord! { x: x, y: y }, + coord! { x: x + offset, y: y + offset }, + ])) + } + 2 => { + // Create a small square around the point + let offset = 0.005; + Geometry::Polygon(Polygon::new( + LineString::new(vec![ + coord! { x: x - offset, y: y - offset }, + coord! { x: x + offset, y: y - offset }, + coord! { x: x + offset, y: y + offset }, + coord! { x: x - offset, y: y + offset }, + coord! { x: x - offset, y: y - offset }, + ]), + vec![], + )) + } + _ => unreachable!(), + }; + geometries.push(geom); + } + + (points, geometries) +} + +fn build_rtree(points: &[Point]) -> geo_index::rtree::RTree { + let mut builder = RTreeBuilder::new(points.len() as u32); + for point in points { + builder.add(point.x(), point.y(), point.x(), point.y()); + } + builder.finish::() +} + +fn benchmark_distance_metrics(c: &mut Criterion) { + let sizes = vec![100, 1000]; + + for size in sizes { + let (points, geometries) = generate_test_data(size); + let tree = build_rtree(&points); + let query_point = Point::new(0.0, 0.0); + + let euclidean = EuclideanDistance; + let haversine = HaversineDistance::default(); + + // Benchmark neighbors_with_distance with different metrics + let mut group = c.benchmark_group("neighbors_with_distance"); + + group.bench_with_input(BenchmarkId::new("euclidean", size), &size, |b, _| { + b.iter(|| { + tree.neighbors_with_distance( + query_point.x(), + query_point.y(), + Some(10), + None, + &euclidean, + ) + }) + }); + + group.bench_with_input(BenchmarkId::new("haversine", size), &size, |b, _| { + b.iter(|| { + tree.neighbors_with_distance( + query_point.x(), + query_point.y(), + Some(10), + None, + &haversine, + ) + }) + }); + + group.finish(); + + // Benchmark neighbors_geometry with different metrics + let mut geom_group = c.benchmark_group("neighbors_geometry"); + let query_geometry = Geometry::Point(query_point); + + geom_group.bench_with_input(BenchmarkId::new("euclidean", size), &size, |b, _| { + let metric = SimpleMetric; + let accessor = SliceGeometryAccessor::new(&geometries); + b.iter(|| tree.neighbors_geometry(&query_geometry, Some(10), None, &metric, &accessor)) + }); + + geom_group.finish(); + } +} + +fn benchmark_distance_calculations(c: &mut Criterion) { + let euclidean = EuclideanDistance; + let haversine = HaversineDistance::default(); + + let p1 = Point::new(-74.0, 40.7); // New York + let p2 = Point::new(-0.1, 51.5); // London + let geom1 = Geometry::Point(p1); + let geom2 = Geometry::Point(p2); + + // Benchmark raw distance calculations + let mut group = c.benchmark_group("distance_calculation"); + + group.bench_function("euclidean_point_to_point", |b| { + b.iter(|| euclidean.distance(p1.x(), p1.y(), p2.x(), p2.y())) + }); + + group.bench_function("haversine_point_to_point", |b| { + b.iter(|| haversine.distance(p1.x(), p1.y(), p2.x(), p2.y())) + }); + + group.bench_function("euclidean_geometry_to_geometry", |b| { + b.iter(|| Euclidean.distance(&geom1, &geom2)) + }); + + // Benchmark bbox distance calculations + let bbox = (-75.0, 40.0, -73.0, 41.0); // Bbox around New York + + group.bench_function("euclidean_distance_to_bbox", |b| { + b.iter(|| euclidean.distance_to_bbox(p2.x(), p2.y(), bbox.0, bbox.1, bbox.2, bbox.3)) + }); + + group.bench_function("haversine_distance_to_bbox", |b| { + b.iter(|| haversine.distance_to_bbox(p2.x(), p2.y(), bbox.0, bbox.1, bbox.2, bbox.3)) + }); + + group.finish(); +} + +fn benchmark_comparison_with_baseline(c: &mut Criterion) { + let (points, _) = generate_test_data(1000); + let tree = build_rtree(&points); + let query_point = Point::new(0.0, 0.0); + + let euclidean = EuclideanDistance; + + let mut group = c.benchmark_group("baseline_comparison"); + + // Original neighbors method (baseline) + group.bench_function("original_neighbors", |b| { + b.iter(|| tree.neighbors(query_point.x(), query_point.y(), Some(10), None)) + }); + + // New neighbors_with_distance method + group.bench_function("neighbors_with_distance_euclidean", |b| { + b.iter(|| { + tree.neighbors_with_distance( + query_point.x(), + query_point.y(), + Some(10), + None, + &euclidean, + ) + }) + }); + + group.finish(); +} + +criterion_group!( + benches, + benchmark_distance_metrics, + benchmark_distance_calculations, + benchmark_comparison_with_baseline +); +criterion_main!(benches); diff --git a/src/rtree/distance.rs b/src/rtree/distance.rs new file mode 100644 index 0000000..e927dd6 --- /dev/null +++ b/src/rtree/distance.rs @@ -0,0 +1,390 @@ +//! Distance metrics for spatial queries. +//! +//! This module provides different distance calculation methods for spatial queries, +//! including Euclidean and Haversine distance calculations. + +use crate::r#type::IndexableNum; +use crate::rtree::r#trait::{axis_dist, SimpleDistanceMetric}; +use geo_0_31::algorithm::{Distance, Euclidean, Haversine}; +use geo_0_31::{Geometry, Point}; + +pub use crate::rtree::r#trait::GeometryAccessor; + +/// A trait for calculating distances between geometries and points. +/// +/// This trait extends `SimpleDistanceMetric` to add geometry-to-geometry distance calculations. +pub trait DistanceMetric: SimpleDistanceMetric { + /// Calculate the distance between two geometries. + /// This method is used by geometry-based neighbor searches to compute the actual + /// distance between a query geometry and an item geometry. + /// + /// TODO: Consider changing to accept `&impl GeometryTrait` instead of concrete + /// `Geometry` type for better flexibility and integration with geo-traits. + /// This would be a non-breaking change since Geometry implements GeometryTrait. + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N; +} + +/// Euclidean distance metric. +/// +/// This is the standard straight-line distance calculation suitable for +/// planar coordinate systems. When working with longitude/latitude coordinates, +/// the unit of distance will be degrees. +#[derive(Debug, Clone, Copy, Default)] +pub struct EuclideanDistance; + +impl SimpleDistanceMetric for EuclideanDistance { + #[inline] + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { + let p1 = Point::new(x1.to_f64().unwrap_or(0.0), y1.to_f64().unwrap_or(0.0)); + let p2 = Point::new(x2.to_f64().unwrap_or(0.0), y2.to_f64().unwrap_or(0.0)); + N::from_f64(Euclidean.distance(p1, p2)).unwrap_or(N::max_value()) + } + + #[inline] + fn distance_to_bbox(&self, x: N, y: N, min_x: N, min_y: N, max_x: N, max_y: N) -> N { + let dx = axis_dist(x, min_x, max_x); + let dy = axis_dist(y, min_y, max_y); + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } +} + +impl DistanceMetric for EuclideanDistance { + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) + } +} + +/// Haversine distance metric. +/// +/// This calculates the great-circle distance between two points on a sphere. +/// It's more accurate for geographic distances than Euclidean distance. +/// The input coordinates should be in longitude/latitude (degrees), and +/// the output distance is in meters. +#[derive(Debug, Clone, Copy)] +pub struct HaversineDistance { + /// Earth's radius in meters + pub earth_radius: f64, +} + +impl Default for HaversineDistance { + fn default() -> Self { + Self { + earth_radius: 6378137.0, // WGS84 equatorial radius in meters + } + } +} + +impl HaversineDistance { + /// Create a new Haversine distance metric with custom Earth radius. + pub fn with_radius(earth_radius: f64) -> Self { + Self { earth_radius } + } +} + +impl SimpleDistanceMetric for HaversineDistance { + fn distance(&self, lon1: N, lat1: N, lon2: N, lat2: N) -> N { + let p1 = Point::new(lon1.to_f64().unwrap_or(0.0), lat1.to_f64().unwrap_or(0.0)); + let p2 = Point::new(lon2.to_f64().unwrap_or(0.0), lat2.to_f64().unwrap_or(0.0)); + N::from_f64(Haversine.distance(p1, p2)).unwrap_or(N::max_value()) + } + + fn distance_to_bbox( + &self, + lon: N, + lat: N, + min_lon: N, + min_lat: N, + max_lon: N, + max_lat: N, + ) -> N { + // For geographic distance to bbox, find the closest point on the bbox + let lon_f = lon.to_f64().unwrap_or(0.0); + let lat_f = lat.to_f64().unwrap_or(0.0); + let min_lon_f = min_lon.to_f64().unwrap_or(0.0); + let min_lat_f = min_lat.to_f64().unwrap_or(0.0); + let max_lon_f = max_lon.to_f64().unwrap_or(0.0); + let max_lat_f = max_lat.to_f64().unwrap_or(0.0); + + let closest_lon = lon_f.clamp(min_lon_f, max_lon_f); + let closest_lat = lat_f.clamp(min_lat_f, max_lat_f); + + let point = Point::new(lon_f, lat_f); + let closest_point = Point::new(closest_lon, closest_lat); + N::from_f64(Haversine.distance(point, closest_point)).unwrap_or(N::max_value()) + } +} + +impl DistanceMetric for HaversineDistance { + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + // For Haversine, use centroid-to-centroid distance as approximation + use geo_0_31::algorithm::Centroid; + let c1 = geom1.centroid().unwrap_or(Point::new(0.0, 0.0)); + let c2 = geom2.centroid().unwrap_or(Point::new(0.0, 0.0)); + N::from_f64(Haversine.distance(c1, c2)).unwrap_or(N::max_value()) + } +} + +/// Simple geometry accessor that wraps a slice of geometries. +/// +/// This accessor provides access to geometries by index for use with distance metrics. +/// +/// # Example +/// ``` +/// use geo_index::rtree::distance::{EuclideanDistance, SliceGeometryAccessor}; +/// use geo_0_31::{Geometry, Point}; +/// +/// let geometries = vec![ +/// Geometry::Point(Point::new(0.0, 0.0)), +/// Geometry::Point(Point::new(1.0, 1.0)), +/// ]; +/// +/// let accessor = SliceGeometryAccessor::new(&geometries); +/// let metric = EuclideanDistance; +/// // Now accessor and metric can be used with neighbors_geometry +/// ``` +pub struct SliceGeometryAccessor<'a> { + geometries: &'a [Geometry], +} + +impl<'a> SliceGeometryAccessor<'a> { + /// Create a new accessor with the given geometries. + pub fn new(geometries: &'a [Geometry]) -> Self { + Self { geometries } + } +} + +impl<'a> GeometryAccessor for SliceGeometryAccessor<'a> { + fn get_geometry(&self, item_index: usize) -> Option<&Geometry> { + self.geometries.get(item_index) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use geo_0_31::{coord, LineString}; + + #[test] + fn test_euclidean_distance() { + let metric = EuclideanDistance; + let distance = metric.distance(0.0f64, 0.0f64, 3.0f64, 4.0f64); + assert!((distance - 5.0f64).abs() < 1e-10); + } + + #[test] + fn test_haversine_distance() { + let metric = HaversineDistance::default(); + // Distance between New York and London (approximately) + let distance = metric.distance(-74.0f64, 40.7f64, -0.1f64, 51.5f64); + // Should be approximately 5585 km + assert!((distance - 5585000.0f64).abs() < 50000.0f64); + } + + #[test] + fn test_euclidean_geometry_distance() { + // Test Euclidean distance between geometries + let point1 = Geometry::Point(Point::new(0.0, 0.0)); + let point2 = Geometry::Point(Point::new(3.0, 4.0)); + let distance: f64 = Euclidean.distance(&point1, &point2); + assert!((distance - 5.0).abs() < 1e-10); + + // Test distance to line + let line = Geometry::LineString(LineString::new(vec![ + coord! { x: 0.0, y: 5.0 }, + coord! { x: 10.0, y: 5.0 }, + ])); + let query = Geometry::Point(Point::new(0.0, 0.0)); + let distance: f64 = Euclidean.distance(&query, &line); + assert!((distance - 5.0).abs() < 1e-10); + } + + #[test] + fn test_wkb_decoding_distance_metric() { + use geozero::{wkb, GeozeroGeometry}; + + /// Custom distance metric that stores WKB-encoded geometries and decodes them on-demand + struct WkbDistanceMetric<'a> { + wkb_data: &'a [Vec], // Array of WKB-encoded geometries + } + + impl<'a> WkbDistanceMetric<'a> { + fn new(wkb_data: &'a [Vec]) -> Self { + Self { wkb_data } + } + + /// Decode WKB data on-demand to get geometry + fn decode_geometry(&self, index: usize) -> Option> { + if index < self.wkb_data.len() { + use geozero::geo_types::GeoWriter; + + let mut geo_writer = GeoWriter::new(); + // Pass the byte slice directly to Wkb + if wkb::Wkb(self.wkb_data[index].as_slice()) + .process_geom(&mut geo_writer) + .is_ok() + { + geo_writer.take_geometry() + } else { + None + } + } else { + None + } + } + } + + impl<'a, N: IndexableNum> SimpleDistanceMetric for WkbDistanceMetric<'a> { + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { + EuclideanDistance.distance(x1, y1, x2, y2) + } + + fn distance_to_bbox(&self, x: N, y: N, min_x: N, min_y: N, max_x: N, max_y: N) -> N { + EuclideanDistance.distance_to_bbox(x, y, min_x, min_y, max_x, max_y) + } + } + + impl<'a, N: IndexableNum> DistanceMetric for WkbDistanceMetric<'a> { + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) + } + } + + // Create some test WKB data (encoded points) + let point1 = Geometry::Point(Point::new(0.0, 0.0)); + let point2 = Geometry::Point(Point::new(3.0, 4.0)); + let point3 = Geometry::Point(Point::new(6.0, 8.0)); + + // Encode geometries to WKB using geozero + use geozero::ToWkb; + let wkb1 = point1.to_wkb(geozero::CoordDimensions::default()).unwrap(); + let wkb2 = point2.to_wkb(geozero::CoordDimensions::default()).unwrap(); + let wkb3 = point3.to_wkb(geozero::CoordDimensions::default()).unwrap(); + let wkb_data = vec![wkb1, wkb2, wkb3]; + + // Create the WKB-based distance metric + let wkb_metric = WkbDistanceMetric::new(&wkb_data); + let query = Geometry::Point(Point::new(1.0, 1.0)); + + // Test distance calculation with on-demand WKB decoding + // Decode geometries and compute distances + let geom0 = wkb_metric.decode_geometry(0).unwrap(); + let dist: f64 = wkb_metric.distance_to_geometry(&query, &geom0); + assert!((dist - 1.414).abs() < 0.01); // Distance from (1,1) to (0,0) + + let geom1 = wkb_metric.decode_geometry(1).unwrap(); + let dist: f64 = wkb_metric.distance_to_geometry(&query, &geom1); + assert!((dist - 3.605).abs() < 0.01); // Distance from (1,1) to (3,4) + + let geom2 = wkb_metric.decode_geometry(2).unwrap(); + let dist: f64 = wkb_metric.distance_to_geometry(&query, &geom2); + assert!((dist - 8.602).abs() < 0.01); // Distance from (1,1) to (6,8) + } + + #[test] + fn test_cached_geometry_distance_metric() { + use std::cell::RefCell; + use std::collections::HashMap; + + /// Custom distance metric with geometry caching to avoid repeated calculations + struct CachedDistanceMetric<'a> { + geometries: &'a [Geometry], + cache: RefCell>>, // Cache for decoded geometries + cache_hits: RefCell, // Track cache performance + cache_misses: RefCell, + } + + impl<'a> CachedDistanceMetric<'a> { + fn new(geometries: &'a [Geometry]) -> Self { + Self { + geometries, + cache: RefCell::new(HashMap::new()), + cache_hits: RefCell::new(0), + cache_misses: RefCell::new(0), + } + } + + /// Get geometry with caching - simulates expensive decode operation + fn get_cached_geometry(&self, index: usize) -> Option> { + if index >= self.geometries.len() { + return None; + } + + // Check cache first + if let Some(cached_geom) = self.cache.borrow().get(&index) { + *self.cache_hits.borrow_mut() += 1; + return Some(cached_geom.clone()); + } + + // Cache miss - "expensive" operation simulation + *self.cache_misses.borrow_mut() += 1; + let geometry = self.geometries[index].clone(); + + // Store in cache + self.cache.borrow_mut().insert(index, geometry.clone()); + Some(geometry) + } + + fn get_cache_stats(&self) -> (usize, usize) { + (*self.cache_hits.borrow(), *self.cache_misses.borrow()) + } + } + + impl<'a, N: IndexableNum> SimpleDistanceMetric for CachedDistanceMetric<'a> { + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { + EuclideanDistance.distance(x1, y1, x2, y2) + } + + fn distance_to_bbox(&self, x: N, y: N, min_x: N, min_y: N, max_x: N, max_y: N) -> N { + EuclideanDistance.distance_to_bbox(x, y, min_x, min_y, max_x, max_y) + } + } + + impl<'a, N: IndexableNum> DistanceMetric for CachedDistanceMetric<'a> { + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) + } + } + + // Create test data + let geometries = vec![ + Geometry::Point(Point::new(0.0, 0.0)), + Geometry::Point(Point::new(3.0, 4.0)), + Geometry::Point(Point::new(6.0, 8.0)), + ]; + + let cached_metric = CachedDistanceMetric::new(&geometries); + let query = Geometry::Point(Point::new(1.0, 1.0)); + + // First access - should be cache misses + let geom0 = cached_metric.get_cached_geometry(0).unwrap(); + let dist1: f64 = cached_metric.distance_to_geometry(&query, &geom0); + + let geom1 = cached_metric.get_cached_geometry(1).unwrap(); + let dist2: f64 = cached_metric.distance_to_geometry(&query, &geom1); + + let geom2 = cached_metric.get_cached_geometry(2).unwrap(); + let dist3: f64 = cached_metric.distance_to_geometry(&query, &geom2); + + assert!((dist1 - 1.414).abs() < 0.01); + assert!((dist2 - 3.605).abs() < 0.01); + assert!((dist3 - 8.602).abs() < 0.01); + + let (hits_after_first, misses_after_first) = cached_metric.get_cache_stats(); + assert_eq!(hits_after_first, 0); // No hits yet + assert_eq!(misses_after_first, 3); // 3 misses + + // Second access to same geometries - should be cache hits + let geom0_cached = cached_metric.get_cached_geometry(0).unwrap(); + let dist1_cached: f64 = cached_metric.distance_to_geometry(&query, &geom0_cached); + + let geom1_cached = cached_metric.get_cached_geometry(1).unwrap(); + let dist2_cached: f64 = cached_metric.distance_to_geometry(&query, &geom1_cached); + + assert!((dist1_cached - 1.414).abs() < 0.01); + assert!((dist2_cached - 3.605).abs() < 0.01); + + let (hits_after_second, misses_after_second) = cached_metric.get_cache_stats(); + assert_eq!(hits_after_second, 2); // 2 cache hits + assert_eq!(misses_after_second, 3); // Still 3 misses total + } +} diff --git a/src/rtree/mod.rs b/src/rtree/mod.rs index 08243a7..dd59622 100644 --- a/src/rtree/mod.rs +++ b/src/rtree/mod.rs @@ -56,6 +56,8 @@ mod builder; mod constants; +#[cfg(feature = "use-geo_0_31")] +pub mod distance; mod index; pub mod sort; mod r#trait; @@ -64,5 +66,5 @@ pub mod util; pub use builder::{RTreeBuilder, DEFAULT_RTREE_NODE_SIZE}; pub use index::{RTree, RTreeMetadata, RTreeRef}; -pub use r#trait::RTreeIndex; +pub use r#trait::{RTreeIndex, SimpleDistanceMetric}; pub use traversal::Node; diff --git a/src/rtree/trait.rs b/src/rtree/trait.rs index 7afafce..d513464 100644 --- a/src/rtree/trait.rs +++ b/src/rtree/trait.rs @@ -2,17 +2,55 @@ use std::cmp::Reverse; use std::collections::{BinaryHeap, VecDeque}; use std::vec; +#[cfg(feature = "use-geo_0_31")] +use geo_0_31::algorithm::BoundingRect; +#[cfg(feature = "use-geo_0_31")] +use geo_0_31::Geometry; use geo_traits::{CoordTrait, RectTrait}; use crate::error::Result; use crate::indices::Indices; use crate::r#type::IndexableNum; +#[cfg(feature = "use-geo_0_31")] +use crate::rtree::distance::DistanceMetric; use crate::rtree::index::{RTree, RTreeRef}; use crate::rtree::traversal::{IntersectionIterator, Node}; use crate::rtree::util::upper_bound; use crate::rtree::RTreeMetadata; use crate::GeoIndexError; +/// A simple distance metric trait that doesn't depend on geo. +/// +/// This trait is used for basic distance calculations without geometry support. +pub trait SimpleDistanceMetric { + /// Calculate the distance between two points (x1, y1) and (x2, y2). + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N; + + /// Calculate the distance from a point to a bounding box. + fn distance_to_bbox(&self, x: N, y: N, min_x: N, min_y: N, max_x: N, max_y: N) -> N; + + /// Return the maximum distance value for this metric. + fn max_distance(&self) -> N { + N::max_value() + } +} + +/// A trait for accessing geometries by index. +/// +/// This trait allows different storage strategies for geometries (direct storage, +/// WKB decoding, caching, etc.) to be used with spatial index queries. +#[cfg(feature = "use-geo_0_31")] +pub trait GeometryAccessor { + /// Get the geometry at the given index. + /// + /// # Arguments + /// * `item_index` - Index of the item to retrieve + /// + /// # Returns + /// A reference to the geometry at the given index, or None if the index is out of bounds + fn get_geometry(&self, item_index: usize) -> Option<&Geometry>; +} + /// A trait for searching and accessing data out of an RTree. pub trait RTreeIndex: Sized { /// A slice representing all the bounding boxes of all elements contained within this tree, @@ -140,6 +178,9 @@ pub trait RTreeIndex: Sized { /// Search items in order of distance from the given point. /// + /// This method uses Euclidean distance by default. For other distance metrics, + /// use [`neighbors_with_distance`]. + /// /// ``` /// use geo_index::rtree::{RTreeBuilder, RTreeIndex, RTreeRef}; /// use geo_index::rtree::sort::HilbertSort; @@ -160,15 +201,43 @@ pub trait RTreeIndex: Sized { y: N, max_results: Option, max_distance: Option, + ) -> Vec { + // Use simple squared distance for backward compatibility + struct SimpleSquaredDistance; + impl SimpleDistanceMetric for SimpleSquaredDistance { + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { + let dx = x2 - x1; + let dy = y2 - y1; + dx * dx + dy * dy + } + fn distance_to_bbox(&self, x: N, y: N, min_x: N, min_y: N, max_x: N, max_y: N) -> N { + let dx = axis_dist(x, min_x, max_x); + let dy = axis_dist(y, min_y, max_y); + dx * dx + dy * dy + } + } + let simple_distance = SimpleSquaredDistance; + self.neighbors_with_simple_distance(x, y, max_results, max_distance, &simple_distance) + } + + /// Search items in order of distance from the given point using a simple distance metric. + /// + /// This is the base method for distance-based neighbor searches that works without the geo feature. + fn neighbors_with_simple_distance + ?Sized>( + &self, + x: N, + y: N, + max_results: Option, + max_distance: Option, + distance_metric: &M, ) -> Vec { let boxes = self.boxes(); let indices = self.indices(); - let max_distance = max_distance.unwrap_or(N::max_value()); + let max_distance = max_distance.unwrap_or(distance_metric.max_distance()); let mut outer_node_index = Some(boxes.len() - 4); let mut queue = BinaryHeap::new(); let mut results: Vec = vec![]; - let max_dist_squared = max_distance * max_distance; 'outer: while let Some(node_index) = outer_node_index { // find the end index of the node @@ -179,10 +248,17 @@ pub trait RTreeIndex: Sized { for pos in (node_index..end).step_by(4) { let index = indices.get(pos >> 2); - let dx = axis_dist(x, boxes[pos], boxes[pos + 2]); - let dy = axis_dist(y, boxes[pos + 1], boxes[pos + 3]); - let dist = dx * dx + dy * dy; - if dist > max_dist_squared { + // Use the custom distance metric for bbox distance calculation + let dist = distance_metric.distance_to_bbox( + x, + y, + boxes[pos], + boxes[pos + 1], + boxes[pos + 2], + boxes[pos + 3], + ); + + if dist > max_distance { continue; } @@ -194,6 +270,7 @@ pub trait RTreeIndex: Sized { })); } else { // leaf item (use odd id) + // Use consistent distance calculation for both nodes and leaf items queue.push(Reverse(NeighborNode { id: (index << 1) + 1, dist, @@ -204,7 +281,7 @@ pub trait RTreeIndex: Sized { // pop items from the queue while !queue.is_empty() && queue.peek().is_some_and(|val| (val.0.id & 1) != 0) { let dist = queue.peek().unwrap().0.dist; - if dist > max_dist_squared { + if dist > max_distance { break 'outer; } let item = queue.pop().unwrap(); @@ -224,6 +301,39 @@ pub trait RTreeIndex: Sized { results } + /// Search items in order of distance from the given point using a custom distance metric. + /// + /// This method allows you to specify a custom distance calculation method, such as + /// Euclidean, Haversine, or Spheroid distance. + /// + /// ``` + /// use geo_index::rtree::{RTreeBuilder, RTreeIndex}; + /// use geo_index::rtree::distance::{EuclideanDistance, HaversineDistance}; + /// use geo_index::rtree::sort::HilbertSort; + /// + /// // Create an RTree with geographic coordinates (longitude, latitude) + /// let mut builder = RTreeBuilder::::new(3); + /// builder.add(-74.0, 40.7, -74.0, 40.7); // New York + /// builder.add(-0.1, 51.5, -0.1, 51.5); // London + /// builder.add(139.7, 35.7, 139.7, 35.7); // Tokyo + /// let tree = builder.finish::(); + /// + /// // Find nearest neighbors using Haversine distance (great-circle distance) + /// let haversine = HaversineDistance::default(); + /// let results = tree.neighbors_with_distance(-74.0, 40.7, Some(2), None, &haversine); + /// ``` + #[cfg(feature = "use-geo_0_31")] + fn neighbors_with_distance + ?Sized>( + &self, + x: N, + y: N, + max_results: Option, + max_distance: Option, + distance_metric: &M, + ) -> Vec { + self.neighbors_with_simple_distance(x, y, max_results, max_distance, distance_metric) + } + /// Search items in order of distance from the given coordinate. fn neighbors_coord( &self, @@ -234,6 +344,161 @@ pub trait RTreeIndex: Sized { self.neighbors(coord.x(), coord.y(), max_results, max_distance) } + /// Search items in order of distance from the given coordinate using a custom distance metric. + #[cfg(feature = "use-geo_0_31")] + fn neighbors_coord_with_distance + ?Sized>( + &self, + coord: &impl CoordTrait, + max_results: Option, + max_distance: Option, + distance_metric: &M, + ) -> Vec { + self.neighbors_with_distance( + coord.x(), + coord.y(), + max_results, + max_distance, + distance_metric, + ) + } + + /// Search items in order of distance from a query geometry using a distance metric and geometry accessor. + /// + /// This method allows searching with geometry-to-geometry distance calculations. + /// The distance metric defines how distances are computed, and the geometry accessor + /// provides access to the actual geometries by index. + /// + /// ``` + /// use geo_index::rtree::{RTreeBuilder, RTreeIndex}; + /// use geo_index::rtree::distance::{EuclideanDistance, SliceGeometryAccessor}; + /// use geo_index::rtree::sort::HilbertSort; + /// use geo_0_31::{Point, Geometry}; + /// + /// // Create an RTree + /// let mut builder = RTreeBuilder::::new(3); + /// builder.add(0., 0., 2., 2.); + /// builder.add(5., 5., 7., 7.); + /// builder.add(10., 10., 12., 12.); + /// let tree = builder.finish::(); + /// + /// // Example geometries + /// let geometries: Vec> = vec![ + /// Geometry::Point(Point::new(1.0, 1.0)), + /// Geometry::Point(Point::new(6.0, 6.0)), + /// Geometry::Point(Point::new(11.0, 11.0)), + /// ]; + /// + /// let metric = EuclideanDistance; + /// let accessor = SliceGeometryAccessor::new(&geometries); + /// let query_geom = Geometry::Point(Point::new(3.0, 3.0)); + /// let results = tree.neighbors_geometry(&query_geom, None, None, &metric, &accessor); + /// ``` + #[cfg(feature = "use-geo_0_31")] + fn neighbors_geometry + ?Sized, A: GeometryAccessor + ?Sized>( + &self, + query_geometry: &Geometry, + max_results: Option, + max_distance: Option, + distance_metric: &M, + accessor: &A, + ) -> Vec { + let boxes = self.boxes(); + let indices = self.indices(); + let max_distance = max_distance.unwrap_or(distance_metric.max_distance()); + + // Get the bounding box of the query geometry + let bounds = query_geometry.bounding_rect(); + let (query_min_x, query_min_y, query_max_x, query_max_y) = if let Some(rect) = bounds { + let min = rect.min(); + let max = rect.max(); + ( + N::from_f64(min.x).unwrap_or(N::zero()), + N::from_f64(min.y).unwrap_or(N::zero()), + N::from_f64(max.x).unwrap_or(N::zero()), + N::from_f64(max.y).unwrap_or(N::zero()), + ) + } else { + // If no bounding box, use origin + (N::zero(), N::zero(), N::zero(), N::zero()) + }; + + let mut outer_node_index = Some(boxes.len() - 4); + let mut queue = BinaryHeap::new(); + let mut results: Vec = vec![]; + + 'outer: while let Some(node_index) = outer_node_index { + // find the end index of the node + let end = (node_index + self.node_size() as usize * 4) + .min(upper_bound(node_index, self.level_bounds())); + + // add child nodes to the queue + for pos in (node_index..end).step_by(4) { + let index = indices.get(pos >> 2); + + let dist = if node_index >= self.num_items() as usize * 4 { + // For internal nodes, use bbox-to-bbox distance as approximation + let center_x = (query_min_x + query_max_x) / (N::one() + N::one()); + let center_y = (query_min_y + query_max_y) / (N::one() + N::one()); + + distance_metric.distance_to_bbox( + center_x, + center_y, + boxes[pos], + boxes[pos + 1], + boxes[pos + 2], + boxes[pos + 3], + ) + } else { + // For leaf items, use geometry-to-geometry distance + if let Some(item_geom) = accessor.get_geometry(index) { + distance_metric.distance_to_geometry(query_geometry, item_geom) + } else { + distance_metric.max_distance() + } + }; + + if dist > max_distance { + continue; + } + + if node_index >= self.num_items() as usize * 4 { + // node (use even id) + queue.push(Reverse(NeighborNode { + id: index << 1, + dist, + })); + } else { + // leaf item (use odd id) + queue.push(Reverse(NeighborNode { + id: (index << 1) + 1, + dist, + })); + } + } + + // pop items from the queue + while !queue.is_empty() && queue.peek().is_some_and(|val| (val.0.id & 1) != 0) { + let dist = queue.peek().unwrap().0.dist; + if dist > max_distance { + break 'outer; + } + let item = queue.pop().unwrap(); + results.push((item.0.id >> 1).try_into().unwrap()); + if max_results.is_some_and(|max_results| results.len() == max_results) { + break 'outer; + } + } + + if let Some(item) = queue.pop() { + outer_node_index = Some(item.0.id >> 1); + } else { + outer_node_index = None; + } + } + + results + } + /// Returns an iterator over the indexes of objects in this and another tree that intersect. /// /// Each returned object is of the form `(u32, u32)`, where the first is the positional @@ -302,9 +567,8 @@ impl RTreeIndex for RTreeRef<'_, N> { } /// 1D distance from a value to a range. -#[allow(dead_code)] #[inline] -fn axis_dist(k: N, min: N, max: N) -> N { +pub(crate) fn axis_dist(k: N, min: N, max: N) -> N { if k < min { min - k } else if k <= max { @@ -345,4 +609,365 @@ mod test { assert_eq!(results, expected); } } + + #[cfg(feature = "use-geo_0_31")] + mod distance_metrics { + use crate::rtree::distance::{EuclideanDistance, HaversineDistance}; + use crate::rtree::r#trait::SimpleDistanceMetric; + use crate::rtree::sort::HilbertSort; + use crate::rtree::{RTreeBuilder, RTreeIndex}; + + #[test] + fn test_euclidean_distance_neighbors() { + let mut builder = RTreeBuilder::::new(3); + builder.add(0., 0., 1., 1.); + builder.add(2., 2., 3., 3.); + builder.add(4., 4., 5., 5.); + let tree = builder.finish::(); + + let euclidean = EuclideanDistance; + let results = tree.neighbors_with_distance(0., 0., None, None, &euclidean); + + // Should return items in order of distance from (0,0) + assert_eq!(results, vec![0, 1, 2]); + } + + #[test] + fn test_haversine_distance_neighbors() { + let mut builder = RTreeBuilder::::new(3); + // Add some geographic points (longitude, latitude) + builder.add(-74.0, 40.7, -74.0, 40.7); // New York + builder.add(-0.1, 51.5, -0.1, 51.5); // London + builder.add(139.7, 35.7, 139.7, 35.7); // Tokyo + let tree = builder.finish::(); + + let haversine = HaversineDistance::default(); + let results = tree.neighbors_with_distance(-74.0, 40.7, None, None, &haversine); + + // From New York, should find New York first, then London, then Tokyo + assert_eq!(results, vec![0, 1, 2]); + } + + #[test] + fn test_backward_compatibility() { + let mut builder = RTreeBuilder::::new(3); + builder.add(0., 0., 1., 1.); + builder.add(2., 2., 3., 3.); + builder.add(4., 4., 5., 5.); + let tree = builder.finish::(); + + // Test that original neighbors method still works + let results_original = tree.neighbors(0., 0., None, None); + + // Test that new method with Euclidean distance gives same results + let euclidean = EuclideanDistance; + let results_new = tree.neighbors_with_distance(0., 0., None, None, &euclidean); + + assert_eq!(results_original, results_new); + } + + #[test] + fn test_max_distance_filtering() { + let mut builder = RTreeBuilder::::new(3); + builder.add(0., 0., 1., 1.); + builder.add(2., 2., 3., 3.); + builder.add(10., 10., 11., 11.); + let tree = builder.finish::(); + + let euclidean = EuclideanDistance; + // Only find neighbors within distance 5 + let results = tree.neighbors_with_distance(0., 0., None, Some(5.0), &euclidean); + + // Should only find first two items, not the distant third one + assert_eq!(results.len(), 2); + assert_eq!(results, vec![0, 1]); + } + + #[test] + #[cfg(feature = "use-geo_0_31")] + fn test_geometry_neighbors_euclidean() { + use crate::r#type::IndexableNum; + use crate::rtree::distance::{DistanceMetric, SliceGeometryAccessor}; + use geo_0_31::algorithm::{Distance, Euclidean}; + use geo_0_31::{Geometry, Point}; + + let mut builder = RTreeBuilder::::new(3); + builder.add(0., 0., 2., 2.); // Item 0 + builder.add(5., 5., 7., 7.); // Item 1 + builder.add(10., 10., 12., 12.); // Item 2 + let tree = builder.finish::(); + + // Geometries corresponding to the bboxes + let geometries: Vec> = vec![ + Geometry::Point(Point::new(1.0, 1.0)), // Item 0 + Geometry::Point(Point::new(6.0, 6.0)), // Item 1 + Geometry::Point(Point::new(11.0, 11.0)), // Item 2 + ]; + + struct SimpleMetric; + impl SimpleDistanceMetric for SimpleMetric { + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { + let dx = x2 - x1; + let dy = y2 - y1; + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } + fn distance_to_bbox( + &self, + x: N, + y: N, + min_x: N, + min_y: N, + max_x: N, + max_y: N, + ) -> N { + let dx = if x < min_x { + min_x - x + } else if x > max_x { + x - max_x + } else { + N::zero() + }; + let dy = if y < min_y { + min_y - y + } else if y > max_y { + y - max_y + } else { + N::zero() + }; + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } + } + impl DistanceMetric for SimpleMetric { + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) + } + } + + let query_geom = Geometry::Point(Point::new(3.0, 3.0)); + let metric = SimpleMetric; + let accessor = SliceGeometryAccessor::new(&geometries); + let results = tree.neighbors_geometry(&query_geom, None, None, &metric, &accessor); + + // Item 0 should be closest to query point (3,3) + assert_eq!(results[0], 0); + assert_eq!(results[1], 1); + assert_eq!(results[2], 2); + } + + #[test] + #[cfg(feature = "use-geo_0_31")] + fn test_geometry_neighbors_linestring() { + use crate::r#type::IndexableNum; + use crate::rtree::distance::{DistanceMetric, SliceGeometryAccessor}; + use geo_0_31::algorithm::{Distance, Euclidean}; + use geo_0_31::{coord, Geometry, LineString, Point}; + + let mut builder = RTreeBuilder::::new(3); + builder.add(0., 0., 10., 0.); // Item 0 - horizontal line + builder.add(5., 5., 15., 5.); // Item 1 - horizontal line higher up + builder.add(0., 10., 10., 10.); // Item 2 - horizontal line at top + let tree = builder.finish::(); + + // Geometries corresponding to the bboxes + let geometries: Vec> = vec![ + Geometry::LineString(LineString::new(vec![ + coord! { x: 0.0, y: 0.0 }, + coord! { x: 10.0, y: 0.0 }, + ])), + Geometry::LineString(LineString::new(vec![ + coord! { x: 5.0, y: 5.0 }, + coord! { x: 15.0, y: 5.0 }, + ])), + Geometry::LineString(LineString::new(vec![ + coord! { x: 0.0, y: 10.0 }, + coord! { x: 10.0, y: 10.0 }, + ])), + ]; + + struct SimpleMetric; + impl SimpleDistanceMetric for SimpleMetric { + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { + let dx = x2 - x1; + let dy = y2 - y1; + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } + fn distance_to_bbox( + &self, + x: N, + y: N, + min_x: N, + min_y: N, + max_x: N, + max_y: N, + ) -> N { + let dx = if x < min_x { + min_x - x + } else if x > max_x { + x - max_x + } else { + N::zero() + }; + let dy = if y < min_y { + min_y - y + } else if y > max_y { + y - max_y + } else { + N::zero() + }; + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } + } + impl DistanceMetric for SimpleMetric { + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) + } + } + + let query_geom = Geometry::Point(Point::new(5.0, 2.0)); + let metric = SimpleMetric; + let accessor = SliceGeometryAccessor::new(&geometries); + let results = tree.neighbors_geometry(&query_geom, None, None, &metric, &accessor); + + // Item 0 (bottom line) should be closest to point (5, 2) + assert_eq!(results[0], 0); + } + + #[test] + #[cfg(feature = "use-geo_0_31")] + fn test_geometry_neighbors_with_max_results() { + use crate::r#type::IndexableNum; + use crate::rtree::distance::{DistanceMetric, SliceGeometryAccessor}; + use geo_0_31::algorithm::{Distance, Euclidean}; + use geo_0_31::{Geometry, Point}; + + let mut builder = RTreeBuilder::::new(5); + for i in 0..5 { + let x = (i * 3) as f64; + builder.add(x, x, x + 1., x + 1.); + } + let tree = builder.finish::(); + + // Create geometries for each bbox + let geometries: Vec> = (0..5) + .map(|i| { + let x = (i * 3) as f64; + Geometry::Point(Point::new(x + 0.5, x + 0.5)) + }) + .collect(); + + struct SimpleMetric; + impl SimpleDistanceMetric for SimpleMetric { + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { + let dx = x2 - x1; + let dy = y2 - y1; + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } + fn distance_to_bbox( + &self, + x: N, + y: N, + min_x: N, + min_y: N, + max_x: N, + max_y: N, + ) -> N { + let dx = if x < min_x { + min_x - x + } else if x > max_x { + x - max_x + } else { + N::zero() + }; + let dy = if y < min_y { + min_y - y + } else if y > max_y { + y - max_y + } else { + N::zero() + }; + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } + } + impl DistanceMetric for SimpleMetric { + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) + } + } + + let query_geom = Geometry::Point(Point::new(5.0, 5.0)); + let metric = SimpleMetric; + let accessor = SliceGeometryAccessor::new(&geometries); + let results = tree.neighbors_geometry(&query_geom, Some(3), None, &metric, &accessor); + + assert_eq!(results.len(), 3); + // Should get the 3 closest items + } + + #[test] + #[cfg(feature = "use-geo_0_31")] + fn test_geometry_neighbors_haversine() { + use crate::r#type::IndexableNum; + use crate::rtree::distance::{DistanceMetric, SliceGeometryAccessor}; + use geo_0_31::algorithm::{Centroid, Distance, Haversine}; + use geo_0_31::{Geometry, Point}; + + let mut builder = RTreeBuilder::::new(3); + // Geographic bounding boxes (lon, lat) + builder.add(-74.1, 40.6, -74.0, 40.7); // New York area + builder.add(-0.2, 51.4, -0.1, 51.5); // London area + builder.add(139.6, 35.6, 139.7, 35.7); // Tokyo area + let tree = builder.finish::(); + + let geometries: Vec> = vec![ + Geometry::Point(Point::new(-74.0, 40.7)), // New York + Geometry::Point(Point::new(-0.1, 51.5)), // London + Geometry::Point(Point::new(139.7, 35.7)), // Tokyo + ]; + + struct HaversineMetric; + impl SimpleDistanceMetric for HaversineMetric { + fn distance(&self, lon1: N, lat1: N, lon2: N, lat2: N) -> N { + let p1 = Point::new(lon1.to_f64().unwrap_or(0.0), lat1.to_f64().unwrap_or(0.0)); + let p2 = Point::new(lon2.to_f64().unwrap_or(0.0), lat2.to_f64().unwrap_or(0.0)); + N::from_f64(Haversine.distance(p1, p2)).unwrap_or(N::max_value()) + } + fn distance_to_bbox( + &self, + lon: N, + lat: N, + min_lon: N, + min_lat: N, + max_lon: N, + max_lat: N, + ) -> N { + let lon_f = lon.to_f64().unwrap_or(0.0); + let lat_f = lat.to_f64().unwrap_or(0.0); + let min_lon_f = min_lon.to_f64().unwrap_or(0.0); + let min_lat_f = min_lat.to_f64().unwrap_or(0.0); + let max_lon_f = max_lon.to_f64().unwrap_or(0.0); + let max_lat_f = max_lat.to_f64().unwrap_or(0.0); + let closest_lon = lon_f.clamp(min_lon_f, max_lon_f); + let closest_lat = lat_f.clamp(min_lat_f, max_lat_f); + let point = Point::new(lon_f, lat_f); + let closest_point = Point::new(closest_lon, closest_lat); + N::from_f64(Haversine.distance(point, closest_point)).unwrap_or(N::max_value()) + } + } + impl DistanceMetric for HaversineMetric { + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + let c1 = geom1.centroid().unwrap_or(Point::new(0.0, 0.0)); + let c2 = geom2.centroid().unwrap_or(Point::new(0.0, 0.0)); + N::from_f64(Haversine.distance(c1, c2)).unwrap_or(N::max_value()) + } + } + + let query_geom = Geometry::Point(Point::new(-74.0, 40.7)); // New York + let metric = HaversineMetric; + let accessor = SliceGeometryAccessor::new(&geometries); + let results = tree.neighbors_geometry(&query_geom, None, None, &metric, &accessor); + + // New York should be closest (distance 0) + assert_eq!(results[0], 0); + } + } } diff --git a/src/rtree/traversal.rs b/src/rtree/traversal.rs index f2f2962..37cb3db 100644 --- a/src/rtree/traversal.rs +++ b/src/rtree/traversal.rs @@ -404,8 +404,8 @@ mod test_issue_42 { use crate::rtree::sort::HilbertSort; use crate::rtree::{RTreeBuilder, RTreeIndex}; - use geo::Polygon; - use geo::{BoundingRect, Geometry}; + use geo_0_31::Polygon; + use geo_0_31::{BoundingRect, Geometry}; use geozero::geo_types::GeoWriter; use geozero::geojson::read_geojson_fc; use rstar::primitives::GeomWithData; diff --git a/src/type.rs b/src/type.rs index 3330f5e..aa79f33 100644 --- a/src/type.rs +++ b/src/type.rs @@ -19,6 +19,29 @@ pub trait IndexableNum: const TYPE_INDEX: u8; /// The number of bytes per element const BYTES_PER_ELEMENT: usize; + + /// Convert to f64 for distance calculations + fn to_f64(self) -> Option { + NumCast::from(self) + } + + /// Convert from f64 for distance calculations + fn from_f64(value: f64) -> Option { + NumCast::from(value) + } + + /// Get the square root of this value + fn sqrt(self) -> Option { + self.to_f64() + .and_then(|value| { + if value >= 0.0 { + Some(value.sqrt()) + } else { + None + } + }) + .and_then(NumCast::from) + } } impl IndexableNum for i8 {