From bf2d5de156b990450621943295099eb3e83239fc Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sat, 31 Jan 2026 10:18:54 +0000 Subject: [PATCH] Rewrite server in rust --- server-rs/Cargo.lock | 2740 +++++++++++++++++++++++++++++++++++++ server-rs/Cargo.toml | 19 + server-rs/src/consts.rs | 11 + server-rs/src/data.rs | 405 ++++++ server-rs/src/index.rs | 130 ++ server-rs/src/main.rs | 109 ++ server-rs/src/routes.rs | 461 +++++++ server/__init__.py | 0 server/config.py | 18 - server/main.py | 35 - server/routes/__init__.py | 0 server/routes/hexagons.py | 172 --- server/routes/pois.py | 322 ----- 13 files changed, 3875 insertions(+), 547 deletions(-) create mode 100644 server-rs/Cargo.lock create mode 100644 server-rs/Cargo.toml create mode 100644 server-rs/src/consts.rs create mode 100644 server-rs/src/data.rs create mode 100644 server-rs/src/index.rs create mode 100644 server-rs/src/main.rs create mode 100644 server-rs/src/routes.rs delete mode 100644 server/__init__.py delete mode 100644 server/config.py delete mode 100644 server/main.py delete mode 100644 server/routes/__init__.py delete mode 100644 server/routes/hexagons.py delete mode 100644 server/routes/pois.py diff --git a/server-rs/Cargo.lock b/server-rs/Cargo.lock new file mode 100644 index 0000000..ddb90e7 --- /dev/null +++ b/server-rs/Cargo.lock @@ -0,0 +1,2740 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ar_archive_writer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +dependencies = [ + "object", +] + +[[package]] +name = "argminmax" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f13d10a41ac8d2ec79ee34178d61e6f47a29c2edfe7ef1721c7383b0359e65" +dependencies = [ + "num-traits", +] + +[[package]] +name = "array-init-cursor" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed51fe0f224d1d4ea768be38c51f9f831dee9d05c163c11fba0b8c44387b1fc3" + +[[package]] +name = "async-compression" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atoi_simd" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a49e05797ca52e312a0c658938b7d00693ef037799ef7187678f212d7684cf" +dependencies = [ + "debug_unsafe", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +dependencies = [ + "serde", +] + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "num-traits", + "windows-link", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf", +] + +[[package]] +name = "comfy-table" +version = "7.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47" +dependencies = [ + "crossterm", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "serde", + "static_assertions", +] + +[[package]] +name = "compression-codecs" +version = "0.4.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags", + "crossterm_winapi", + "document-features", + "parking_lot", + "rustix", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "debug_unsafe" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85d3cef41d236720ed453e102153a53e4cc3d2fde848c0078a50cf249e8e3e5b" + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "ethnum" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fast-float2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "float_eq" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a80e3145d8ad11ba0995949bbcf48b9df2be62772b3d351ef017dff6ecb853" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "h3o" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537b141fa7998c2c993b9431247f6e2eb69d606bd51173ab85394792f3a7cdf7" +dependencies = [ + "ahash", + "either", + "float_eq", + "h3o-bit", + "libm", +] + +[[package]] +name = "h3o-bit" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b42eb4efef1f96510ae1a33b2682562a677d504641e9903a77bf5c666b9013e" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", + "rayon", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", + "rayon", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[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.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lz4" +version = "1.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20b523e860d03443e98350ceaac5e71c6ba89aea7d960769ec3ce37f4de5af4" +dependencies = [ + "lz4-sys", +] + +[[package]] +name = "lz4-sys" +version = "1.11.1+lz4-1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "now" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89e9874397a1f0a52fc1f197a8effd9735223cb2390e9dcc83ac6cd02923d0" +dependencies = [ + "chrono", +] + +[[package]] +name = "ntapi" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[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", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "planus" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1691dd09e82f428ce8d6310bd6d5da2557c82ff17694d2a32cad7242aea89f" +dependencies = [ + "array-init-cursor", +] + +[[package]] +name = "polars" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72571dde488ecccbe799798bf99ab7308ebdb7cf5d95bcc498dbd5a132f0da4d" +dependencies = [ + "getrandom 0.2.17", + "polars-arrow", + "polars-core", + "polars-error", + "polars-io", + "polars-lazy", + "polars-ops", + "polars-parquet", + "polars-sql", + "polars-time", + "polars-utils", + "version_check", +] + +[[package]] +name = "polars-arrow" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6611c758d52e799761cc25900666b71552e6c929d88052811bc9daad4b3321a8" +dependencies = [ + "ahash", + "atoi_simd", + "bytemuck", + "chrono", + "chrono-tz", + "dyn-clone", + "either", + "ethnum", + "getrandom 0.2.17", + "hashbrown 0.15.5", + "itoa", + "lz4", + "num-traits", + "parking_lot", + "polars-arrow-format", + "polars-error", + "polars-schema", + "polars-utils", + "simdutf8", + "streaming-iterator", + "strength_reduce", + "strum_macros", + "version_check", + "zstd", +] + +[[package]] +name = "polars-arrow-format" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b0ef2474af9396b19025b189d96e992311e6a47f90c53cd998b36c4c64b84c" +dependencies = [ + "planus", + "serde", +] + +[[package]] +name = "polars-compute" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332f2547dbb27599a8ffe68e56159f5996ba03d1dad0382ccb62c109ceacdeb6" +dependencies = [ + "atoi_simd", + "bytemuck", + "chrono", + "either", + "fast-float2", + "itoa", + "num-traits", + "polars-arrow", + "polars-error", + "polars-utils", + "ryu", + "strength_reduce", + "version_check", +] + +[[package]] +name = "polars-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796d06eae7e6e74ed28ea54a8fccc584ebac84e6cf0e1e9ba41ffc807b169a01" +dependencies = [ + "ahash", + "bitflags", + "bytemuck", + "chrono", + "chrono-tz", + "comfy-table", + "either", + "hashbrown 0.14.5", + "hashbrown 0.15.5", + "indexmap", + "itoa", + "num-traits", + "once_cell", + "polars-arrow", + "polars-compute", + "polars-error", + "polars-row", + "polars-schema", + "polars-utils", + "rand", + "rand_distr", + "rayon", + "regex", + "strum_macros", + "thiserror", + "version_check", + "xxhash-rust", +] + +[[package]] +name = "polars-error" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d6529cae0d1db5ed690e47de41fac9b35ae0c26d476830c2079f130887b847" +dependencies = [ + "polars-arrow-format", + "regex", + "simdutf8", + "thiserror", +] + +[[package]] +name = "polars-expr" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e639991a8ad4fb12880ab44bcc3cf44a5703df003142334d9caf86d77d77e7" +dependencies = [ + "ahash", + "bitflags", + "hashbrown 0.15.5", + "num-traits", + "once_cell", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-io", + "polars-ops", + "polars-plan", + "polars-row", + "polars-time", + "polars-utils", + "rand", + "rayon", +] + +[[package]] +name = "polars-io" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719a77e94480f6be090512da196e378cbcbeb3584c6fe1134c600aee906e38ab" +dependencies = [ + "ahash", + "async-trait", + "atoi_simd", + "bytes", + "chrono", + "fast-float2", + "futures", + "glob", + "hashbrown 0.15.5", + "home", + "itoa", + "memchr", + "memmap2", + "num-traits", + "once_cell", + "percent-encoding", + "polars-arrow", + "polars-core", + "polars-error", + "polars-parquet", + "polars-schema", + "polars-time", + "polars-utils", + "rayon", + "regex", + "ryu", + "simdutf8", + "tokio", + "tokio-util", +] + +[[package]] +name = "polars-lazy" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a731a672dfc8ac38c1f73c9a4b2ae38d2fc8ac363bfb64c5f3a3e072ffc5ad" +dependencies = [ + "ahash", + "bitflags", + "chrono", + "memchr", + "once_cell", + "polars-arrow", + "polars-core", + "polars-expr", + "polars-io", + "polars-mem-engine", + "polars-ops", + "polars-pipe", + "polars-plan", + "polars-stream", + "polars-time", + "polars-utils", + "rayon", + "version_check", +] + +[[package]] +name = "polars-mem-engine" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33442189bcbf2e2559aa7914db3835429030a13f4f18e43af5fba9d1b018cf12" +dependencies = [ + "memmap2", + "polars-arrow", + "polars-core", + "polars-error", + "polars-expr", + "polars-io", + "polars-ops", + "polars-plan", + "polars-time", + "polars-utils", + "rayon", +] + +[[package]] +name = "polars-ops" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbb83218b0c216104f0076cd1a005128be078f958125f3d59b094ee73d78c18e" +dependencies = [ + "ahash", + "argminmax", + "base64", + "bytemuck", + "chrono", + "chrono-tz", + "either", + "hashbrown 0.15.5", + "hex", + "indexmap", + "memchr", + "num-traits", + "once_cell", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-error", + "polars-schema", + "polars-utils", + "rayon", + "regex", + "regex-syntax", + "strum_macros", + "unicode-normalization", + "unicode-reverse", + "version_check", +] + +[[package]] +name = "polars-parquet" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c60ee85535590a38db6c703a21be4cb25342e40f573f070d1e16f9d84a53ac7" +dependencies = [ + "ahash", + "async-stream", + "base64", + "brotli", + "bytemuck", + "ethnum", + "flate2", + "futures", + "hashbrown 0.15.5", + "lz4", + "num-traits", + "polars-arrow", + "polars-compute", + "polars-error", + "polars-parquet-format", + "polars-utils", + "simdutf8", + "snap", + "streaming-decompression", + "zstd", +] + +[[package]] +name = "polars-parquet-format" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c025243dcfe8dbc57e94d9f82eb3bef10b565ab180d5b99bed87fd8aea319ce1" +dependencies = [ + "async-trait", + "futures", +] + +[[package]] +name = "polars-pipe" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d238fb76698f56e51ddfa89b135e4eda56a4767c6e8859eed0ab78386fcd52" +dependencies = [ + "crossbeam-channel", + "crossbeam-queue", + "enum_dispatch", + "futures", + "hashbrown 0.15.5", + "num-traits", + "once_cell", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-expr", + "polars-io", + "polars-ops", + "polars-plan", + "polars-row", + "polars-utils", + "rayon", + "uuid", + "version_check", +] + +[[package]] +name = "polars-plan" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f03533a93aa66127fcb909a87153a3c7cfee6f0ae59f497e73d7736208da54c" +dependencies = [ + "ahash", + "bitflags", + "bytemuck", + "bytes", + "chrono", + "chrono-tz", + "either", + "hashbrown 0.15.5", + "memmap2", + "num-traits", + "once_cell", + "percent-encoding", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-io", + "polars-ops", + "polars-parquet", + "polars-time", + "polars-utils", + "rayon", + "recursive", + "regex", + "strum_macros", + "version_check", +] + +[[package]] +name = "polars-row" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf47f7409f8e75328d7d034be390842924eb276716d0458607be0bddb8cc839" +dependencies = [ + "bitflags", + "bytemuck", + "polars-arrow", + "polars-compute", + "polars-error", + "polars-utils", +] + +[[package]] +name = "polars-schema" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416621ae82b84466cf4ff36838a9b0aeb4a67e76bd3065edc8c9cb7da19b1bc7" +dependencies = [ + "indexmap", + "polars-error", + "polars-utils", + "version_check", +] + +[[package]] +name = "polars-sql" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edaab553b90aa4d6743bb538978e1982368acb58a94408d7dd3299cad49c7083" +dependencies = [ + "hex", + "polars-core", + "polars-error", + "polars-lazy", + "polars-ops", + "polars-plan", + "polars-time", + "polars-utils", + "rand", + "regex", + "serde", + "sqlparser", +] + +[[package]] +name = "polars-stream" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498997b656c779610c1496b3d96a59fe569ef22a5b81ccfe5325cb3df8dff2fd" +dependencies = [ + "atomic-waker", + "crossbeam-deque", + "crossbeam-utils", + "futures", + "memmap2", + "parking_lot", + "pin-project-lite", + "polars-core", + "polars-error", + "polars-expr", + "polars-io", + "polars-mem-engine", + "polars-ops", + "polars-parquet", + "polars-plan", + "polars-utils", + "rand", + "rayon", + "recursive", + "slotmap", + "tokio", + "version_check", +] + +[[package]] +name = "polars-time" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d192efbdab516d28b3fab1709a969e3385bd5cda050b7c9aa9e2502a01fda879" +dependencies = [ + "atoi_simd", + "bytemuck", + "chrono", + "chrono-tz", + "now", + "num-traits", + "once_cell", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-error", + "polars-ops", + "polars-utils", + "rayon", + "regex", + "strum_macros", +] + +[[package]] +name = "polars-utils" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f6c8166a4a7fbc15b87c81645ed9e1f0651ff2e8c96cafc40ac5bf43441a10" +dependencies = [ + "ahash", + "bytemuck", + "bytes", + "compact_str", + "hashbrown 0.15.5", + "indexmap", + "libc", + "memmap2", + "num-traits", + "once_cell", + "polars-error", + "rand", + "raw-cpuid", + "rayon", + "stacker", + "sysinfo", + "version_check", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "property-map-server" +version = "0.1.0" +dependencies = [ + "axum", + "h3o", + "polars", + "rayon", + "rustc-hash", + "serde", + "serde_json", + "tokio", + "tower-http", +] + +[[package]] +name = "psm" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa96cb91275ed31d6da3e983447320c4eb219ac180fa1679a0889ff32861e2d" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "recursive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0786a43debb760f491b1bc0269fe5e84155353c67482b9e60d0cfb596054b43e" +dependencies = [ + "recursive-proc-macro-impl", + "stacker", +] + +[[package]] +name = "recursive-proc-macro-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +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 = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "sqlparser" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05a528114c392209b3264855ad491fcce534b94a38771b0a0b97a79379275ce8" +dependencies = [ + "log", +] + +[[package]] +name = "stacker" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "streaming-decompression" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf6cc3b19bfb128a8ad11026086e31d3ce9ad23f8ea37354b31383a187c44cf3" +dependencies = [ + "fallible-streaming-iterator", +] + +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "sysinfo" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "windows", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "async-compression", + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-reverse" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6f4888ebc23094adfb574fdca9fdc891826287a6397d2cd28802ffd6f20c76" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[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" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link", + "windows-result 0.4.1", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "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-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "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", + "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.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.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.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.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.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.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.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 = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "zerocopy" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zlib-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + +[[package]] +name = "zmij" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/server-rs/Cargo.toml b/server-rs/Cargo.toml new file mode 100644 index 0000000..5ddaa7d --- /dev/null +++ b/server-rs/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "property-map-server" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = "0.8" +tower-http = { version = "0.6", features = ["cors", "fs", "compression-gzip"] } +tokio = { version = "1", features = ["full"] } +polars = { version = "0.46", features = ["parquet", "lazy", "dtype-struct", "dtype-u8", "dtype-u16", "dtype-i8", "dtype-i16"] } +h3o = "0.7" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +rayon = "1" +rustc-hash = "2" + +[profile.release] +opt-level = 3 +lto = true diff --git a/server-rs/src/consts.rs b/server-rs/src/consts.rs new file mode 100644 index 0000000..7ccf49e --- /dev/null +++ b/server-rs/src/consts.rs @@ -0,0 +1,11 @@ +/// Lower percentile for feature range reporting +pub const FEATURE_PERCENTILE_LOW: f64 = 2.0; + +/// Upper percentile for feature range reporting +pub const FEATURE_PERCENTILE_HIGH: f64 = 98.0; + +pub const HISTOGRAM_BINS: usize = 100; + +/// H3 resolutions to precompute at startup (covers typical zoom levels) +pub const H3_PRECOMPUTE_MIN: u8 = 4; +pub const H3_PRECOMPUTE_MAX: u8 = 12; diff --git a/server-rs/src/data.rs b/server-rs/src/data.rs new file mode 100644 index 0000000..16928c3 --- /dev/null +++ b/server-rs/src/data.rs @@ -0,0 +1,405 @@ +use polars::prelude::*; +use polars::lazy::frame::LazyFrame; +use rayon::prelude::*; +use serde::Serialize; +use std::path::Path; + +use crate::consts::{FEATURE_PERCENTILE_LOW, FEATURE_PERCENTILE_HIGH, HISTOGRAM_BINS, H3_PRECOMPUTE_MIN, H3_PRECOMPUTE_MAX}; + +/// Columns to exclude from feature discovery (not numeric features) +const EXCLUDED_COLUMNS: &[&str] = &["lat", "lon"]; + +/// H3 valid resolution range (0-15) +pub const MIN_RESOLUTION: u8 = 0; +pub const MAX_RESOLUTION: u8 = 15; +pub const DEFAULT_RESOLUTION: u8 = 8; + +/// Returns true if the polars DataType is numeric (integer or float) +fn is_numeric_dtype(dtype: &DataType) -> bool { + matches!( + dtype, + DataType::Int8 + | DataType::Int16 + | DataType::Int32 + | DataType::Int64 + | DataType::UInt8 + | DataType::UInt16 + | DataType::UInt32 + | DataType::UInt64 + | DataType::Float32 + | DataType::Float64 + ) +} + +/// Histogram for a single feature column +#[derive(Serialize, Clone)] +pub struct Histogram { + /// Left edge of first bin + pub min: f64, + /// Right edge of last bin + pub max: f64, + /// Width of each bin + pub bin_width: f64, + /// Count of values in each bin + pub counts: Vec, +} + +/// Precomputed statistics for a single feature +pub struct FeatureStats { + pub p_low: f64, + pub p_high: f64, + pub histogram: Histogram, +} + +/// Columnar storage for all property data. +/// Feature values use NaN as the null sentinel. +pub struct PropertyData { + pub lat: Vec, + pub lon: Vec, + /// Dynamically discovered numeric feature column names + pub feature_names: Vec, + /// Number of feature columns + pub num_features: usize, + /// Row-major flat array: feature_data[row * num_features + feat_idx]. + /// NaN = null. Contiguous layout for cache-friendly per-row access. + pub feature_data: Vec, + /// Precomputed stats (percentiles + histogram) for each feature + pub feature_stats: Vec, +} + +/// Approximate a percentile from a histogram using linear interpolation. +/// `p` is in [0, 100]. `total` is the sum of all bin counts. +fn percentile_from_histogram(counts: &[u64], min: f64, bin_width: f64, total: usize, p: f64) -> f64 { + let target = (p / 100.0) * (total as f64 - 1.0); + let mut cumulative = 0u64; + for (i, &c) in counts.iter().enumerate() { + let prev = cumulative; + cumulative += c; + if cumulative as f64 > target { + // Interpolate within this bin + let frac = if c > 0 { + (target - prev as f64) / c as f64 + } else { + 0.0 + }; + return min + (i as f64 + frac) * bin_width; + } + } + // Fallback: right edge of last bin + min + counts.len() as f64 * bin_width +} + +/// Build a histogram and compute approximate percentiles in O(n) — no sort needed. +fn compute_feature_stats(vals: &[f64]) -> FeatureStats { + // Single pass: min, max, count (skipping NaN) + let mut min = f64::INFINITY; + let mut max = f64::NEG_INFINITY; + let mut count = 0usize; + for &v in vals { + if !v.is_nan() { + if v < min { min = v; } + if v > max { max = v; } + count += 1; + } + } + + if count == 0 { + return FeatureStats { + p_low: 0.0, + p_high: 0.0, + histogram: Histogram { + min: 0.0, + max: 0.0, + bin_width: 1.0, + counts: vec![0; HISTOGRAM_BINS], + }, + }; + } + + // Build histogram over full range (second pass, no sort) + let range = if max == min { 1.0 } else { max - min }; + let bin_max = min + range * (1.0 + 1e-9); + let bin_width = (bin_max - min) / HISTOGRAM_BINS as f64; + + let mut counts = vec![0u64; HISTOGRAM_BINS]; + for &v in vals { + if !v.is_nan() { + let bin = ((v - min) / bin_width) as usize; + counts[bin.min(HISTOGRAM_BINS - 1)] += 1; + } + } + + // Approximate percentiles from the histogram + let p_low = percentile_from_histogram(&counts, min, bin_width, count, FEATURE_PERCENTILE_LOW); + let p_high = percentile_from_histogram(&counts, min, bin_width, count, FEATURE_PERCENTILE_HIGH); + + FeatureStats { + p_low, + p_high, + histogram: Histogram { + min, + max, + bin_width, + counts, + }, + } +} + +/// Convert a polars Column to Vec using NaN for null values +fn column_to_f64_vec(c: &Column) -> Vec { + let s = c.cast(&DataType::Float64).unwrap(); + let ca = s.f64().unwrap(); + ca.into_iter().map(|v| v.unwrap_or(f64::NAN)).collect() +} + +/// Precompute H3 cell IDs for all rows at commonly used resolutions. +/// Returns a Vec indexed by resolution (0..16), where non-precomputed +/// resolutions have an empty Vec. +pub fn precompute_h3(lat: &[f64], lon: &[f64]) -> Vec> { + eprintln!( + "Precomputing H3 cells for resolutions {}..{}...", + H3_PRECOMPUTE_MIN, H3_PRECOMPUTE_MAX + ); + + let resolutions: Vec = (H3_PRECOMPUTE_MIN..=H3_PRECOMPUTE_MAX).collect(); + let computed: Vec<(u8, Vec)> = resolutions + .into_par_iter() + .map(|res| { + let h3_res = h3o::Resolution::try_from(res).unwrap(); + let cells: Vec = lat + .iter() + .zip(lon.iter()) + .map(|(&la, &lo)| { + h3o::LatLng::new(la, lo) + .map(|c| u64::from(c.to_cell(h3_res))) + .unwrap_or(0) + }) + .collect(); + eprintln!(" Resolution {} done ({} cells)", res, cells.len()); + (res, cells) + }) + .collect(); + + let mut result: Vec> = (0..16).map(|_| Vec::new()).collect(); + for (res, cells) in computed { + result[res as usize] = cells; + } + + eprintln!("H3 precomputation complete."); + result +} + +impl PropertyData { + pub fn load(parquet_path: &Path) -> Self { + eprintln!("Loading parquet from {:?}...", parquet_path); + + // Scan schema to discover numeric feature columns + let mut lf = LazyFrame::scan_parquet(parquet_path, Default::default()) + .expect("Failed to scan parquet"); + let schema = lf.collect_schema().expect("Failed to read schema"); + + let feature_names: Vec = schema + .iter() + .filter(|(name, dtype)| { + is_numeric_dtype(dtype) && !EXCLUDED_COLUMNS.contains(&name.as_str()) + }) + .map(|(name, _)| name.to_string()) + .collect(); + + let num_features = feature_names.len(); + eprintln!("Discovered {} numeric feature columns", num_features); + + // Read only the columns we need + let mut cols_needed: Vec = vec!["lat".into(), "lon".into()]; + cols_needed.extend(feature_names.iter().cloned()); + + let df = LazyFrame::scan_parquet(parquet_path, Default::default()) + .expect("Failed to scan parquet") + .select( + cols_needed + .iter() + .map(|c| col(c.as_str()).cast(DataType::Float64)) + .collect::>(), + ) + .collect() + .expect("Failed to read parquet"); + + let row_count = df.height(); + eprintln!("Loaded {} rows", row_count); + + // Extract lat/lon using bulk iterator + let lat_series = df.column("lat").unwrap().cast(&DataType::Float64).unwrap(); + let lat: Vec = lat_series.f64().unwrap().into_iter().map(|v| v.unwrap_or(0.0)).collect(); + + let lon_series = df.column("lon").unwrap().cast(&DataType::Float64).unwrap(); + let lon: Vec = lon_series.f64().unwrap().into_iter().map(|v| v.unwrap_or(0.0)).collect(); + + // Extract feature columns (column-major, for cache-friendly histogram computation) + eprintln!("Extracting feature columns..."); + let col_major: Vec> = feature_names + .iter() + .map(|name| { + let s = df.column(name.as_str()).unwrap(); + column_to_f64_vec(s) + }) + .collect(); + + // Compute histograms in parallel (column-major is ideal for per-column iteration) + eprintln!("Computing histograms..."); + let feature_stats: Vec = col_major + .par_iter() + .enumerate() + .map(|(i, vals)| { + let stats = compute_feature_stats(vals); + eprintln!( + " {}: p{}={:.2}, p{}={:.2}, {} bins", + feature_names[i], + FEATURE_PERCENTILE_LOW, stats.p_low, + FEATURE_PERCENTILE_HIGH, stats.p_high, + stats.histogram.counts.len() + ); + stats + }) + .collect(); + + // Sort all rows by spatial locality so that grid queries access + // contiguous memory (sequential reads instead of random DRAM accesses). + // Uses the same 0.01° grid cell as the spatial index for the sort key. + eprintln!("Sorting rows by spatial locality..."); + let grid_cell_size = 0.01_f64; + let min_lat_val = lat.iter().cloned().fold(f64::INFINITY, f64::min) - grid_cell_size; + let min_lon_val = lon.iter().cloned().fold(f64::INFINITY, f64::min) - grid_cell_size; + let max_lon_val = lon.iter().cloned().fold(f64::NEG_INFINITY, f64::max) + grid_cell_size; + let grid_cols = ((max_lon_val - min_lon_val) / grid_cell_size).ceil() as u64 + 1; + + let mut perm: Vec = (0..row_count as u32).collect(); + perm.sort_unstable_by_key(|&i| { + let r = ((lat[i as usize] - min_lat_val) / grid_cell_size) as u64; + let c = ((lon[i as usize] - min_lon_val) / grid_cell_size) as u64; + r * grid_cols + c + }); + + // Apply permutation to lat/lon + let lat: Vec = perm.iter().map(|&i| lat[i as usize]).collect(); + let lon: Vec = perm.iter().map(|&i| lon[i as usize]).collect(); + + // Transpose to row-major AND apply spatial permutation in one pass. + // Result: all features for one row are contiguous, and spatially + // nearby rows are adjacent in memory. + eprintln!("Transposing to row-major layout (spatially sorted)..."); + let mut feature_data = vec![f64::NAN; row_count * num_features]; + for (new_row, &old_row) in perm.iter().enumerate() { + let old = old_row as usize; + let dst_base = new_row * num_features; + for (feat_idx, col_vec) in col_major.iter().enumerate() { + feature_data[dst_base + feat_idx] = col_vec[old]; + } + } + + eprintln!("Data loading complete."); + + PropertyData { + lat, + lon, + feature_names, + num_features, + feature_data, + feature_stats, + } + } +} + +/// Point of Interest data +#[derive(Serialize)] +pub struct POI { + pub id: String, + pub name: String, + pub category: String, + pub lat: f64, + pub lng: f64, + pub emoji: String, +} + +/// Columnar storage for POI data +pub struct POIData { + pub id: Vec, + pub name: Vec, + pub category: Vec, + pub lat: Vec, + pub lng: Vec, + pub emoji: Vec, +} + +impl POIData { + pub fn load(parquet_path: &Path) -> Self { + eprintln!("Loading POI data from {:?}...", parquet_path); + + let df = LazyFrame::scan_parquet(parquet_path, Default::default()) + .expect("Failed to scan POI parquet") + .collect() + .expect("Failed to read POI parquet"); + + let row_count = df.height(); + eprintln!("Loaded {} POIs", row_count); + + // Extract columns + let id: Vec = df.column("id") + .unwrap() + .str() + .unwrap() + .into_iter() + .map(|v| v.unwrap_or("").to_string()) + .collect(); + + let name: Vec = df.column("name") + .unwrap() + .str() + .unwrap() + .into_iter() + .map(|v| v.unwrap_or("").to_string()) + .collect(); + + let category: Vec = df.column("category") + .unwrap() + .str() + .unwrap() + .into_iter() + .map(|v| v.unwrap_or("").to_string()) + .collect(); + + let lat: Vec = df.column("lat") + .unwrap() + .f64() + .unwrap() + .into_iter() + .map(|v| v.unwrap_or(0.0)) + .collect(); + + let lng: Vec = df.column("lng") + .unwrap() + .f64() + .unwrap() + .into_iter() + .map(|v| v.unwrap_or(0.0)) + .collect(); + + let emoji: Vec = df.column("emoji") + .unwrap() + .str() + .unwrap() + .into_iter() + .map(|v| v.unwrap_or("").to_string()) + .collect(); + + eprintln!("POI data loading complete."); + + POIData { + id, + name, + category, + lat, + lng, + emoji, + } + } +} diff --git a/server-rs/src/index.rs b/server-rs/src/index.rs new file mode 100644 index 0000000..5b705ed --- /dev/null +++ b/server-rs/src/index.rs @@ -0,0 +1,130 @@ +/// Grid-based spatial index for fast rectangle queries over property rows. +/// +/// Divides the UK bounding box into cells of ~0.01 degrees (~1km), +/// each storing indices of rows whose lat/lon falls within that cell. + +pub struct GridIndex { + min_lat: f64, + min_lon: f64, + cell_size: f64, + cols: usize, + rows: usize, + /// cells[row * cols + col] = vec of row indices + cells: Vec>, +} + +impl GridIndex { + /// Build the grid index from lat/lon arrays. + pub fn build(lat: &[f64], lon: &[f64], cell_size: f64) -> Self { + // Compute bounding box with a small margin + let mut min_lat = f64::INFINITY; + let mut max_lat = f64::NEG_INFINITY; + let mut min_lon = f64::INFINITY; + let mut max_lon = f64::NEG_INFINITY; + + for i in 0..lat.len() { + let la = lat[i]; + let lo = lon[i]; + if la < min_lat { + min_lat = la; + } + if la > max_lat { + max_lat = la; + } + if lo < min_lon { + min_lon = lo; + } + if lo > max_lon { + max_lon = lo; + } + } + + // Add margin + min_lat -= cell_size; + min_lon -= cell_size; + max_lat += cell_size; + max_lon += cell_size; + + let rows = ((max_lat - min_lat) / cell_size).ceil() as usize + 1; + let cols = ((max_lon - min_lon) / cell_size).ceil() as usize + 1; + + eprintln!( + "Building grid index: {}x{} cells ({} total), cell_size={}", + rows, + cols, + rows * cols, + cell_size + ); + + let mut cells: Vec> = vec![Vec::new(); rows * cols]; + + for i in 0..lat.len() { + let r = ((lat[i] - min_lat) / cell_size) as usize; + let c = ((lon[i] - min_lon) / cell_size) as usize; + let idx = r * cols + c; + cells[idx].push(i as u32); + } + + eprintln!("Grid index built."); + + GridIndex { + min_lat, + min_lon, + cell_size, + cols, + rows, + cells, + } + } + + /// Query all row indices within the given bounding box. + pub fn query(&self, south: f64, west: f64, north: f64, east: f64) -> Vec { + let (r_min, r_max, c_min, c_max) = self.clamp_bounds(south, west, north, east); + + let mut result = Vec::new(); + for r in r_min..=r_max { + let row_start = r * self.cols; + for c in c_min..=c_max { + result.extend_from_slice(&self.cells[row_start + c]); + } + } + + result + } + + /// Iterate all row indices in bounds without allocating a Vec. + #[inline] + pub fn for_each_in_bounds( + &self, + south: f64, + west: f64, + north: f64, + east: f64, + mut f: impl FnMut(u32), + ) { + let (r_min, r_max, c_min, c_max) = self.clamp_bounds(south, west, north, east); + + for r in r_min..=r_max { + let row_start = r * self.cols; + for c in c_min..=c_max { + for &row_idx in &self.cells[row_start + c] { + f(row_idx); + } + } + } + } + + fn clamp_bounds(&self, south: f64, west: f64, north: f64, east: f64) -> (usize, usize, usize, usize) { + let r_min = ((south - self.min_lat) / self.cell_size) as isize; + let r_max = ((north - self.min_lat) / self.cell_size) as isize; + let c_min = ((west - self.min_lon) / self.cell_size) as isize; + let c_max = ((east - self.min_lon) / self.cell_size) as isize; + + let r_min = r_min.max(0) as usize; + let r_max = (r_max.min(self.rows as isize - 1)).max(0) as usize; + let c_min = c_min.max(0) as usize; + let c_max = (c_max.min(self.cols as isize - 1)).max(0) as usize; + + (r_min, r_max, c_min, c_max) + } +} diff --git a/server-rs/src/main.rs b/server-rs/src/main.rs new file mode 100644 index 0000000..a3bc3ea --- /dev/null +++ b/server-rs/src/main.rs @@ -0,0 +1,109 @@ +mod consts; +mod data; +mod index; +mod routes; + +use std::path::PathBuf; +use std::sync::Arc; + +use axum::routing::get; +use axum::Router; +use tower_http::compression::CompressionLayer; +use tower_http::cors::{Any, CorsLayer}; +use tower_http::services::ServeDir; + +use routes::AppState; + +#[tokio::main] +async fn main() { + let parquet_path = PathBuf::from( + std::env::args() + .nth(1) + .unwrap_or_else(|| "data_sources/processed/wide.parquet".to_string()), + ); + if !parquet_path.exists() { + eprintln!("Error: {} not found.", parquet_path.display()); + std::process::exit(1); + } + + // Load property data and build indices + let property_data = data::PropertyData::load(&parquet_path); + let grid = index::GridIndex::build(&property_data.lat, &property_data.lon, 0.01); + let h3_cells = data::precompute_h3(&property_data.lat, &property_data.lon); + + // Load POI data and build spatial index + // Derive POI path from the data parquet path (same directory) + let poi_path = parquet_path + .parent() + .and_then(|p| p.parent()) + .map(|p| p.join("filtered_uk_pois.parquet")) + .unwrap_or_else(|| PathBuf::from("data_sources/filtered_uk_pois.parquet")); + + let poi_data = if poi_path.exists() { + data::POIData::load(&poi_path) + } else { + eprintln!("Warning: {} not found. POI endpoints will be unavailable.", poi_path.display()); + data::POIData { + id: Vec::new(), + name: Vec::new(), + category: Vec::new(), + lat: Vec::new(), + lng: Vec::new(), + emoji: Vec::new(), + } + }; + let poi_grid = index::GridIndex::build(&poi_data.lat, &poi_data.lng, 0.01); + + let state = Arc::new(AppState { + data: property_data, + grid, + h3_cells, + poi_data, + poi_grid, + }); + + let cors = CorsLayer::new() + .allow_origin(Any) + .allow_methods(Any) + .allow_headers(Any); + + // API routes + let state_features = state.clone(); + let state_hexagons = state.clone(); + let state_pois = state.clone(); + let state_poi_categories = state.clone(); + + let api = Router::new() + .route( + "/api/features", + get(move || routes::get_features(state_features.clone())), + ) + .route( + "/api/hexagons", + get(move |query| routes::get_hexagons(state_hexagons.clone(), query)), + ) + .route( + "/api/pois", + get(move |query| routes::get_pois(state_pois.clone(), query)), + ) + .route( + "/api/poi-categories", + get(move || routes::get_poi_categories(state_poi_categories.clone())), + ); + + // Static file serving for frontend + let frontend_dist = PathBuf::from("frontend/dist"); + let app = if frontend_dist.exists() { + api.fallback_service(ServeDir::new(frontend_dist)) + } else { + api + }; + + let app = app.layer(cors).layer(CompressionLayer::new().gzip(true)); + + let addr = "0.0.0.0:8001"; + eprintln!("Server listening on {}", addr); + + let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + axum::serve(listener, app).await.unwrap(); +} diff --git a/server-rs/src/routes.rs b/server-rs/src/routes.rs new file mode 100644 index 0000000..baa3099 --- /dev/null +++ b/server-rs/src/routes.rs @@ -0,0 +1,461 @@ +use std::fmt::Write; +use std::sync::Arc; + +use axum::extract::Query; +use axum::http::StatusCode; +use axum::response::{IntoResponse, Json}; +use rustc_hash::FxHashMap; +use serde::{Deserialize, Serialize}; + +use crate::data::{Histogram, PropertyData, POIData, POI, DEFAULT_RESOLUTION, MAX_RESOLUTION, MIN_RESOLUTION}; +use crate::index::GridIndex; + +/// Shared application state +pub struct AppState { + pub data: PropertyData, + pub grid: GridIndex, + /// h3_cells[resolution][row_idx] = precomputed H3 cell ID. + /// Empty Vec for resolutions not precomputed. + pub h3_cells: Vec>, + pub poi_data: POIData, + pub poi_grid: GridIndex, +} + +const BOUNDS_BUFFER_PERCENT: f64 = 0.2; + +// ── /api/features ── + +#[derive(Serialize)] +pub struct FeatureInfo { + name: String, + min: f64, + max: f64, + label: String, + histogram: Histogram, +} + +#[derive(Serialize)] +pub struct FeaturesResponse { + features: Vec, +} + +fn snake_to_label(name: &str) -> String { + name.split('_') + .map(|word| { + let mut chars = word.chars(); + match chars.next() { + None => String::new(), + Some(c) => { + let mut s = c.to_uppercase().to_string(); + s.extend(chars); + s + } + } + }) + .collect::>() + .join(" ") +} + +pub async fn get_features(state: Arc) -> Json { + let features = state + .data + .feature_names + .iter() + .enumerate() + .map(|(i, name): (usize, &String)| { + let stats = &state.data.feature_stats[i]; + FeatureInfo { + name: name.clone(), + min: stats.p_low, + max: stats.p_high, + label: snake_to_label(name), + histogram: stats.histogram.clone(), + } + }) + .collect(); + + Json(FeaturesResponse { features }) +} + +// ── /api/hexagons ── + +#[derive(Deserialize)] +pub struct HexagonParams { + resolution: Option, + bounds: Option, + /// Comma-separated filters: `name:min:max,...` + /// Rows must have non-NaN values within [min,max] for each filter. + filters: Option, +} + +struct ParsedFilter { + feat_idx: usize, + min: f64, + max: f64, +} + +/// Per-cell accumulator for aggregating features +struct CellAgg { + count: u32, + mins: Vec, + maxs: Vec, +} + +impl CellAgg { + fn new(num_features: usize) -> Self { + CellAgg { + count: 0, + mins: vec![f64::INFINITY; num_features], + maxs: vec![f64::NEG_INFINITY; num_features], + } + } + + /// Add a row using row-major feature_data layout. + /// feature_data[row * num_features + feat_idx] — all features for one row + /// are contiguous, so this reads a single cache line per ~8 features. + #[inline] + fn add_row(&mut self, feature_data: &[f64], row: usize, num_features: usize) { + self.count += 1; + let base = row * num_features; + let row_slice = &feature_data[base..base + num_features]; + for (i, &v) in row_slice.iter().enumerate() { + if v.is_finite() { + if v < self.mins[i] { + self.mins[i] = v; + } + if v > self.maxs[i] { + self.maxs[i] = v; + } + } + } + } + +} + +/// Write the hexagons JSON response directly to a String buffer, +/// avoiding serde_json::Value allocations entirely. +fn write_hexagons_json( + buf: &mut String, + groups: &FxHashMap, + min_keys: &[String], + max_keys: &[String], + num_features: usize, +) { + buf.push_str("{\"features\":["); + let mut first = true; + for (&cell_id, agg) in groups { + if !first { + buf.push(','); + } + first = false; + + let cell = h3o::CellIndex::try_from(cell_id).unwrap(); + write!(buf, "{{\"h3\":\"{}\",\"count\":{}", cell, agg.count).unwrap(); + + for i in 0..num_features { + if agg.mins[i] != f64::INFINITY { + write!( + buf, + ",\"{}\":{},\"{}\":{}", + min_keys[i], agg.mins[i], max_keys[i], agg.maxs[i] + ) + .unwrap(); + } + } + buf.push('}'); + } + buf.push_str("]}"); +} + +pub async fn get_hexagons( + state: Arc, + Query(params): Query, +) -> Result { + let resolution = params.resolution.unwrap_or(DEFAULT_RESOLUTION); + if resolution > MAX_RESOLUTION { + return Err(( + StatusCode::BAD_REQUEST, + format!( + "resolution must be between {} and {}", + MIN_RESOLUTION, MAX_RESOLUTION + ), + )); + } + + let bounds_str = params + .bounds + .ok_or((StatusCode::BAD_REQUEST, "bounds parameter is required".into()))?; + + let parts: Vec = bounds_str + .split(',') + .map(|s| s.trim().parse::()) + .collect::, _>>() + .map_err(|_| { + ( + StatusCode::BAD_REQUEST, + "Invalid bounds format. Use: south,west,north,east".into(), + ) + })?; + + if parts.len() != 4 { + return Err(( + StatusCode::BAD_REQUEST, + "Invalid bounds format. Use: south,west,north,east".into(), + )); + } + + let (mut south, mut west, mut north, mut east) = (parts[0], parts[1], parts[2], parts[3]); + + // Apply bounds buffer (20%) + let lat_range = north - south; + let lng_range = east - west; + south -= lat_range * BOUNDS_BUFFER_PERCENT; + north += lat_range * BOUNDS_BUFFER_PERCENT; + west -= lng_range * BOUNDS_BUFFER_PERCENT; + east += lng_range * BOUNDS_BUFFER_PERCENT; + + // Quantize to 0.01 degree precision + let precision = 0.01; + south = (south / precision).floor() * precision; + west = (west / precision).floor() * precision; + north = (north / precision).ceil() * precision; + east = (east / precision).ceil() * precision; + + // Parse filters: `name:min:max,...` + let parsed_filters: Vec = params + .filters + .as_deref() + .filter(|s| !s.is_empty()) + .map(|s| { + s.split(',') + .filter_map(|entry| { + let parts: Vec<&str> = entry.splitn(3, ':').collect(); + if parts.len() != 3 { + return None; + } + let name = parts[0].trim(); + let min = parts[1].trim().parse::().ok()?; + let max = parts[2].trim().parse::().ok()?; + let feat_idx = state.data.feature_names.iter().position(|n| n == name)?; + Some(ParsedFilter { feat_idx, min, max }) + }) + .collect() + }) + .unwrap_or_default(); + + // Move CPU-heavy work off the async executor + let json_body = tokio::task::spawn_blocking(move || { + let t0 = std::time::Instant::now(); + + let num_features = state.data.num_features; + let feature_data = &state.data.feature_data; + + // Pre-compute JSON key strings once + let min_keys: Vec = state + .data + .feature_names + .iter() + .map(|n| format!("min_{}", n)) + .collect(); + let max_keys: Vec = state + .data + .feature_names + .iter() + .map(|n| format!("max_{}", n)) + .collect(); + + // Use precomputed H3 cells if available + let h3_cells_for_res: Option<&[u64]> = state + .h3_cells + .get(resolution as usize) + .filter(|v| !v.is_empty()) + .map(|v| v.as_slice()); + + // Aggregate using FxHashMap (fast non-crypto hash for integer keys) + // and grid visitor (no intermediate Vec allocation) + let mut groups: FxHashMap = FxHashMap::default(); + + // Row-level filter check: value must be non-NaN and within [min, max] + let row_passes = |row: usize| -> bool { + parsed_filters.iter().all(|f| { + let v = feature_data[row * num_features + f.feat_idx]; + v.is_finite() && v >= f.min && v <= f.max + }) + }; + + if let Some(precomputed) = h3_cells_for_res { + // Fast path: precomputed H3 + visitor pattern + state.grid.for_each_in_bounds(south, west, north, east, |row_idx| { + let row = row_idx as usize; + if !row_passes(row) { + return; + } + let cell_id = precomputed[row]; + groups + .entry(cell_id) + .or_insert_with(|| CellAgg::new(num_features)) + .add_row(feature_data, row, num_features); + }); + } else { + // Fallback: compute H3 on-the-fly + let h3_res = h3o::Resolution::try_from(resolution).unwrap(); + state.grid.for_each_in_bounds(south, west, north, east, |row_idx| { + let row = row_idx as usize; + if !row_passes(row) { + return; + } + let cell_id = h3o::LatLng::new(state.data.lat[row], state.data.lon[row]) + .map(|c| u64::from(c.to_cell(h3_res))) + .unwrap_or(0); + groups + .entry(cell_id) + .or_insert_with(|| CellAgg::new(num_features)) + .add_row(feature_data, row, num_features); + }); + } + + let t_agg = t0.elapsed(); + + // Write JSON directly (no serde_json::Value allocation overhead) + let mut json_buf = String::with_capacity(groups.len() * 128); + write_hexagons_json( + &mut json_buf, + &groups, + &min_keys, + &max_keys, + num_features, + ); + + let t_total = t0.elapsed(); + eprintln!( + "hexagons: res={} cells={} agg={:?} json={:?} total={:?} bytes={}", + resolution, + groups.len(), + t_agg, + t_total - t_agg, + t_total, + json_buf.len() + ); + + json_buf + }) + .await + .unwrap(); + + Ok(([("content-type", "application/json")], json_body)) +} + +// ── /api/pois ── + +#[derive(Deserialize)] +pub struct POIParams { + bounds: Option, + /// Comma-separated list of categories to filter by + categories: Option, +} + +#[derive(Serialize)] +pub struct POIsResponse { + pois: Vec, +} + +pub async fn get_pois( + state: Arc, + Query(params): Query, +) -> Result, (StatusCode, String)> { + let bounds_str = params + .bounds + .ok_or((StatusCode::BAD_REQUEST, "bounds parameter is required".into()))?; + + let parts: Vec = bounds_str + .split(',') + .map(|s| s.trim().parse::()) + .collect::, _>>() + .map_err(|_| { + ( + StatusCode::BAD_REQUEST, + "Invalid bounds format. Use: south,west,north,east".into(), + ) + })?; + + if parts.len() != 4 { + return Err(( + StatusCode::BAD_REQUEST, + "Invalid bounds format. Use: south,west,north,east".into(), + )); + } + + let (south, west, north, east) = (parts[0], parts[1], parts[2], parts[3]); + + // Parse category filter if provided + let category_filter: Option> = params + .categories + .as_deref() + .filter(|s| !s.is_empty()) + .map(|s| s.split(',').map(|c| c.trim().to_string()).collect()); + + // Move CPU-heavy work off the async executor + let result = tokio::task::spawn_blocking(move || { + // Spatial query using grid index + let row_indices = state.poi_grid.query(south, west, north, east); + + let pois: Vec = row_indices + .iter() + .filter_map(|&row_idx| { + let row = row_idx as usize; + + // Apply category filter if specified + if let Some(ref categories) = category_filter { + if !categories.contains(&state.poi_data.category[row]) { + return None; + } + } + + Some(POI { + id: state.poi_data.id[row].clone(), + name: state.poi_data.name[row].clone(), + category: state.poi_data.category[row].clone(), + lat: state.poi_data.lat[row], + lng: state.poi_data.lng[row], + emoji: state.poi_data.emoji[row].clone(), + }) + }) + .take(5000) + .collect(); + + POIsResponse { pois } + }) + .await + .unwrap(); + + Ok(Json(result)) +} + +// ── /api/poi-categories ── + +#[derive(Serialize)] +pub struct POICategoriesResponse { + categories: Vec, +} + +pub async fn get_poi_categories(state: Arc) -> Json { + // Compute unique categories + let result = tokio::task::spawn_blocking(move || { + let mut categories: Vec = state + .poi_data + .category + .iter() + .cloned() + .collect::>() + .into_iter() + .collect(); + + categories.sort(); + + POICategoriesResponse { categories } + }) + .await + .unwrap(); + + Json(result) +} diff --git a/server/__init__.py b/server/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/server/config.py b/server/config.py deleted file mode 100644 index fcb27d9..0000000 --- a/server/config.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Server configuration - imports shared values from pipeline config.""" - -from pipeline.config import ( - AGGREGATES_DIR, - H3_RESOLUTIONS as VALID_RESOLUTIONS, - DEFAULT_H3_RESOLUTION as DEFAULT_RESOLUTION, -) - -# Extra area to return beyond requested bounds (0.2 = 20%) -# Makes panning smoother by preloading nearby hexagons -BOUNDS_BUFFER_PERCENT = 0.2 - -__all__ = [ - "AGGREGATES_DIR", - "VALID_RESOLUTIONS", - "DEFAULT_RESOLUTION", - "BOUNDS_BUFFER_PERCENT", -] diff --git a/server/main.py b/server/main.py deleted file mode 100644 index d404a51..0000000 --- a/server/main.py +++ /dev/null @@ -1,35 +0,0 @@ -from contextlib import asynccontextmanager -from pathlib import Path -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware -from fastapi.staticfiles import StaticFiles - -from server.routes import hexagons, pois - - -@asynccontextmanager -async def lifespan(app: FastAPI): - # Startup: preload all parquet files - hexagons.preload_dataframes() - pois.preload_pois() - yield - # Shutdown: nothing to clean up - - -app = FastAPI(title="Property Map API", lifespan=lifespan) - -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=False, # Cannot use True with wildcard origins - allow_methods=["*"], - allow_headers=["*"], -) - -app.include_router(hexagons.router, prefix="/api") -app.include_router(pois.router, prefix="/api") - -# Mount static files for production (frontend build) -frontend_dist = Path(__file__).parent.parent / "frontend" / "dist" -if frontend_dist.exists(): - app.mount("/", StaticFiles(directory=frontend_dist, html=True), name="static") diff --git a/server/routes/__init__.py b/server/routes/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/server/routes/hexagons.py b/server/routes/hexagons.py deleted file mode 100644 index c12177e..0000000 --- a/server/routes/hexagons.py +++ /dev/null @@ -1,172 +0,0 @@ -import math -from functools import lru_cache -from fastapi import APIRouter, Query, HTTPException -import polars as pl -import h3 - -from tqdm import tqdm - -from server.config import ( - AGGREGATES_DIR, - VALID_RESOLUTIONS, - DEFAULT_RESOLUTION, - BOUNDS_BUFFER_PERCENT, -) - -router = APIRouter() - -# Cache loaded dataframes in memory (one per resolution) -_df_cache: dict[int, pl.DataFrame] = {} - -# Discovered features (computed once on first load) -_features_cache: list[dict] | None = None - - -def _snake_to_label(name: str) -> str: - """Convert snake_case feature name to a human-readable label.""" - return name.replace("_", " ").title() - - -def _discover_features(df: pl.DataFrame) -> list[dict]: - """Discover features from column pairs min_X / max_X.""" - features = [] - seen = set() - for col in df.columns: - if col.startswith("min_"): - name = col[4:] - max_col = f"max_{name}" - if max_col in df.columns and name not in seen: - seen.add(name) - global_min = df[col].min() - global_max = df[max_col].max() - if global_min is not None and global_max is not None: - features.append( - { - "name": name, - "min": float(global_min), - "max": float(global_max), - "label": _snake_to_label(name), - } - ) - return features - - -def preload_dataframes() -> None: - """Load all resolution dataframes into cache on startup.""" - for resolution in tqdm(VALID_RESOLUTIONS, desc="Loading parquet files"): - get_cached_df(resolution) - - -def get_cached_df(resolution: int) -> pl.DataFrame | None: - """Get cached dataframe for resolution, loading from disk if needed.""" - if resolution not in _df_cache: - parquet_path = AGGREGATES_DIR / f"res{resolution}.parquet" - if not parquet_path.exists(): - return None - # Load and add H3 cell centroids for fast bbox filtering - df = pl.read_parquet(parquet_path) - - # Pre-compute cell centroids for bbox filtering - centroids = [h3.cell_to_latlng(cell) for cell in df["h3"].to_list()] - df = df.with_columns( - [ - pl.Series("_lat", [c[0] for c in centroids]), - pl.Series("_lng", [c[1] for c in centroids]), - ] - ) - _df_cache[resolution] = df - return _df_cache[resolution] - - -def get_features() -> list[dict]: - """Get discovered features, computing from the first available resolution.""" - global _features_cache - if _features_cache is None: - for resolution in VALID_RESOLUTIONS: - df = get_cached_df(resolution) - if df is not None: - _features_cache = _discover_features(df) - break - if _features_cache is None: - _features_cache = [] - return _features_cache - - -@router.get("/features") -async def get_features_endpoint() -> dict: - """Return discovered feature metadata with global min/max ranges.""" - return {"features": get_features()} - - -@lru_cache(maxsize=128) -def query_hexagons_cached( - resolution: int, - bounds_tuple: tuple[float, float, float, float], -) -> list[dict]: - """Cached query - returns features list.""" - south, west, north, east = bounds_tuple - - df = get_cached_df(resolution) - if df is None: - return [] - - # Fast bbox filter using pre-computed centroids - df = df.filter( - (pl.col("_lat") >= south) - & (pl.col("_lat") <= north) - & (pl.col("_lng") >= west) - & (pl.col("_lng") <= east) - ) - - # Drop internal centroid columns before returning - df = df.drop("_lat", "_lng") - - return df.to_dicts() - - -@router.get("/hexagons") -async def get_hexagons( - resolution: int = Query( - DEFAULT_RESOLUTION, - ge=min(VALID_RESOLUTIONS), - le=max(VALID_RESOLUTIONS), - description=f"H3 resolution ({min(VALID_RESOLUTIONS)}-{max(VALID_RESOLUTIONS)})", - ), - bounds: str | None = Query(None, description="Bounding box: south,west,north,east"), -) -> dict: - """Get aggregated property data as hexagons within bounds.""" - if resolution not in VALID_RESOLUTIONS: - resolution = DEFAULT_RESOLUTION - - if not bounds: - raise HTTPException(status_code=400, detail="bounds parameter is required") - - try: - south, west, north, east = map(float, bounds.split(",")) - except ValueError: - raise HTTPException( - status_code=400, detail="Invalid bounds format. Use: south,west,north,east" - ) - - # Expand bounds by buffer percentage for smoother panning - lat_range = north - south - lng_range = east - west - lat_buffer = lat_range * BOUNDS_BUFFER_PERCENT - lng_buffer = lng_range * BOUNDS_BUFFER_PERCENT - south -= lat_buffer - north += lat_buffer - west -= lng_buffer - east += lng_buffer - - # Round bounds to reduce cache misses (0.01 degree ~ 1km precision) - precision = 0.01 - bounds_tuple = ( - math.floor(south / precision) * precision, - math.floor(west / precision) * precision, - math.ceil(north / precision) * precision, - math.ceil(east / precision) * precision, - ) - - features = query_hexagons_cached(resolution, bounds_tuple) - - return {"features": features} diff --git a/server/routes/pois.py b/server/routes/pois.py deleted file mode 100644 index edf48db..0000000 --- a/server/routes/pois.py +++ /dev/null @@ -1,322 +0,0 @@ -"""POI (Points of Interest) API endpoint.""" - -from pathlib import Path - -from fastapi import APIRouter, Query -import polars as pl - -router = APIRouter() - -DATA_FILE = Path("data_sources/uk_pois.parquet") - -# Group definitions: maps a group key to its display metadata and the -# individual POI categories it contains. Categories are matched against -# the values that actually exist in the loaded parquet so that the -# selector only shows groups with real data. -_GROUP_DEFS: dict[str, dict] = { - "schools": { - "emoji": "🏫", - "label": "Schools", - "categories": ["school", "preschool", "college_university", "library"], - }, - "healthcare": { - "emoji": "🏥", - "label": "Healthcare", - "categories": [ - "doctor", - "dentist", - "pharmacy", - "hospital", - "public_health_clinic", - "veterinary", - "nursing_home", - "social_facility", - ], - }, - "transport": { - "emoji": "🚉", - "label": "Transport", - "categories": [ - "train_station", - "bus_station", - "bus_stop", - "metro_station", - "light_rail_station", - "tram_stop", - "ferry_terminal", - "airport", - ], - }, - "parks": { - "emoji": "🌳", - "label": "Parks & Leisure", - "categories": [ - "park", - "national_park", - "nature_reserve", - "dog_park", - "playground", - "garden", - "sports_centre", - "swimming_pool", - "gym", - "golf_course", - "marina", - ], - }, - "emergency": { - "emoji": "🚨", - "label": "Emergency", - "categories": ["police_department", "fire_department"], - }, - "supermarkets": { - "emoji": "🛒", - "label": "Supermarkets & Grocery", - "categories": [ - "supermarket", - "grocery_store", - "convenience_store", - "bakery", - "butcher", - "greengrocer", - "deli", - ], - }, - "shopping": { - "emoji": "🛍️", - "label": "Shopping", - "categories": [ - "department_store", - "clothing_store", - "shoe_store", - "electronics_store", - "hardware_store", - "furniture_store", - "bookshop", - "newsagent", - "charity_shop", - "shopping_centre", - "optician", - "off_licence", - ], - }, - "food_drink": { - "emoji": "🍽️", - "label": "Food & Drink", - "categories": [ - "restaurant", - "cafe", - "pub", - "bar", - "fast_food", - "food_court", - "ice_cream", - "beer_garden", - ], - }, - "personal_care": { - "emoji": "💇", - "label": "Personal Care", - "categories": [ - "hairdresser", - "beauty_salon", - "laundry", - "dry_cleaning", - ], - }, - "finance": { - "emoji": "🏦", - "label": "Finance", - "categories": ["bank", "atm", "bureau_de_change"], - }, - "entertainment": { - "emoji": "🎭", - "label": "Entertainment & Culture", - "categories": [ - "cinema", - "theatre", - "nightclub", - "community_centre", - "arts_centre", - "museum", - "gallery", - "attraction", - "zoo", - "theme_park", - "viewpoint", - ], - }, - "accommodation": { - "emoji": "🏨", - "label": "Accommodation", - "categories": [ - "hotel", - "hostel", - "guest_house", - "campsite", - "caravan_site", - ], - }, - "religion": { - "emoji": "🛐", - "label": "Places of Worship", - "categories": ["place_of_worship"], - }, - "government": { - "emoji": "🏛️", - "label": "Government & Public", - "categories": [ - "town_hall", - "courthouse", - "post_office", - "prison", - "public_toilets", - ], - }, - "automotive": { - "emoji": "⛽", - "label": "Automotive", - "categories": [ - "petrol_station", - "ev_charging", - "car_dealer", - "car_repair", - "parking", - "bicycle_parking", - ], - }, - "recycling": { - "emoji": "♻️", - "label": "Recycling & Waste", - "categories": ["recycling", "waste_disposal"], - }, -} - -# Built at startup from the data — only groups whose member categories -# actually appear in the parquet file are included. -_active_groups: dict[str, dict] = {} - -# Reverse lookup: category value -> group key (built at startup) -_cat_to_group: dict[str, str] = {} - -# Cache the dataframe -_df_cache: pl.DataFrame | None = None - - -def _load_and_build() -> pl.DataFrame | None: - """Load the parquet, build category groups from actual data.""" - global _df_cache, _active_groups, _cat_to_group - - if not DATA_FILE.exists(): - return None - - df = pl.read_parquet(DATA_FILE).select("id", "name", "category", "lat", "lng") - - # Distinct categories present in the data - data_categories: set[str] = set( - df.select("category").unique().to_series().to_list() - ) - - # Per-category counts for the response - counts: dict[str, int] = dict( - df.group_by("category") - .agg(pl.len().alias("n")) - .iter_rows() - ) - - # Build reverse map from every known category to its group - cat_to_group: dict[str, str] = {} - for key, gdef in _GROUP_DEFS.items(): - for cat in gdef["categories"]: - cat_to_group[cat] = key - - # Only keep categories that belong to a known group - known_categories = data_categories & cat_to_group.keys() - - # Build active groups — only those with at least one matching category - active: dict[str, dict] = {} - for key, gdef in _GROUP_DEFS.items(): - present = [c for c in gdef["categories"] if c in known_categories] - if present: - active[key] = { - "emoji": gdef["emoji"], - "label": gdef["label"], - "categories": present, - "count": sum(counts.get(c, 0) for c in present), - } - - _active_groups = active - _cat_to_group = cat_to_group - - # Filter dataframe to only known categories - _df_cache = df.filter(pl.col("category").is_in(known_categories)) - return _df_cache - - -def get_df() -> pl.DataFrame | None: - """Return cached POI dataframe, loading if necessary.""" - if _df_cache is None: - return _load_and_build() - return _df_cache - - -def preload_pois() -> None: - """Preload POI data on startup.""" - df = _load_and_build() - if df is not None: - n_groups = len(_active_groups) - print(f"Loaded {len(df):,} POIs across {n_groups} category groups") - - -@router.get("/pois") -async def get_pois( - categories: str = Query(..., description="Comma-separated category groups"), - bounds: str = Query(..., description="Bounding box: south,west,north,east"), -) -> dict: - """Get POIs within bounds for specified category groups.""" - df = get_df() - if df is None: - return {"features": []} - - try: - south, west, north, east = map(float, bounds.split(",")) - except ValueError: - return {"features": []} - - requested_groups = [g.strip() for g in categories.split(",")] - cats_to_include: set[str] = set() - for group in requested_groups: - if group in _active_groups: - cats_to_include.update(_active_groups[group]["categories"]) - - if not cats_to_include: - return {"features": []} - - filtered = df.filter( - (pl.col("lat") >= south) - & (pl.col("lat") <= north) - & (pl.col("lng") >= west) - & (pl.col("lng") <= east) - & (pl.col("category").is_in(cats_to_include)) - ) - - MAX_POIS = 5000 - if len(filtered) > MAX_POIS: - filtered = filtered.sample(n=MAX_POIS, seed=42) - - return {"features": filtered.to_dicts()} - - -@router.get("/poi-categories") -async def get_poi_categories() -> dict: - """Get available POI category groups derived from loaded data.""" - return { - "categories": { - key: { - "emoji": group["emoji"], - "label": group["label"], - "count": group["count"], - } - for key, group in _active_groups.items() - } - }