<p align="center">
<a href="https://github.com/pkvartsianyi/spatio">
<img src="assets/images/logo-min.png" height="60" alt="Spatio Logo">
</a>
</p>
<h1 align="center">Spatio</h1>
<p align="center">
<a href="https://opensource.org/licenses/MIT">
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT">
</a>
<a href="https://crates.io/crates/spatio">
<img src="https://img.shields.io/crates/v/spatio.svg" alt="Crates.io">
</a>
<a href="https://pypi.org/project/spatio">
<img src="https://img.shields.io/pypi/v/spatio.svg" alt="PyPI">
</a>
<a href="https://pkvartsianyi.github.io/spatio/">
<img src="https://img.shields.io/badge/Docs-Available-blue.svg" alt="Documentation">
</a>
<a href="https://docs.rs/spatio">
<img src="https://img.shields.io/badge/docs.rs-spatio-66c2a5" alt="Rust Docs">
</a>
</p>
**Spatio** is a compact and efficient **embedded spatio-temporal database** written in Rust.
It's designed for **real-time 2D and 3D location data**, with **low memory usage**, **optional persistence**, and **native Python bindings**.
No SQL parser, no external dependencies, and requires no setup.
## Quick Start
### Python
```bash
pip install spatio
```
```python
import spatio
db = spatio.Spatio.memory()
# Store a point (longitude, latitude)
nyc = spatio.Point(-74.0060, 40.7128)
db.insert_point("cities", nyc, b"New York")
# Find nearby points
nearby = db.query_within_radius("cities", nyc, 100000, 10)
```
### Rust
```toml
[dependencies]
spatio = "0.1"
```
```rust
use spatio::prelude::*;
fn main() -> Result<()> {
let mut db = Spatio::memory()?;
let nyc = Point::new(-74.0060, 40.7128);
db.insert_point("cities", &nyc, b"New York", None)?;
let nearby = db.query_within_radius("cities", &nyc, 100_000.0, 10)?;
println!("Found {} cities", nearby.len());
Ok(())
}
```
## Features
**Spatial queries:** Radius search, bounding box, K-nearest-neighbors, polygon containment
**3D support:** Full altitude-aware indexing and queries
**Trajectories:** Track movement over time
**Distance metrics:** Haversine, Geodesic, Rhumb, Euclidean
**TTL:** Auto-expire old data
**Persistence:** Snapshots (default) or append-only file (AOF)
**Namespaces:** Logical data separation in one database
## Coordinate Order
**Important:** Spatio uses `(longitude, latitude)` order everywhere - same as GeoJSON and most GIS tools.
```rust
// Correct: lon, lat
let point = Point::new(-74.0060, 40.7128); // NYC
```
This matches the mathematical (x, y) convention and makes GeoJSON interop trivial.
## Examples
### 2D Spatial
```rust
// Insert points
db.insert_point("pois", &Point::new(-74.0, 40.7), b"Restaurant", None)?;
// Find nearby (within 1km)
let nearby = db.query_within_radius("pois", ¢er, 1000.0, 10)?;
// Bounding box query
let in_box = db.find_within_bounds("pois", 40.0, -75.0, 41.0, -73.0, 100)?;
// K-nearest neighbors
let nearest = db.knn("pois", ¢er, 5, 10_000.0, DistanceMetric::Haversine)?;
```
### 3D Spatial
```rust
use spatio::Point3d;
// Track drones with altitude
let drone = Point3d::new(-74.0060, 40.7128, 100.0);
db.insert_point_3d("drones", &drone, b"Alpha", None)?;
// 3D sphere query
let nearby = db.query_within_sphere_3d("drones", &drone, 200.0, 10)?;
// Cylindrical query (altitude range + radius)
let in_cylinder = db.query_within_cylinder_3d(
"drones", &drone, 50.0, 150.0, 1000.0, 10
)?;
```
### Trajectories
```rust
use spatio::TemporalPoint;
let path = vec![
TemporalPoint {
point: Point::new(-74.00, 40.71),
timestamp: UNIX_EPOCH + Duration::from_secs(100)
},
TemporalPoint {
point: Point::new(-74.01, 40.72),
timestamp: UNIX_EPOCH + Duration::from_secs(200)
},
];
db.insert_trajectory("truck:001", &path, None)?;
// Query movement history
let history = db.query_trajectory("truck:001", 100, 300)?;
```
### TTL (Time-To-Live)
```rust
use spatio::SetOptions;
// Data expires in 1 hour
let opts = SetOptions::with_ttl(Duration::from_secs(3600));
db.insert("session:123", b"data", Some(opts))?;
// Expired items return None
let value = db.get("session:123")?; // None if expired
// Clean up expired items manually
let removed = db.cleanup_expired()?;
```
**Important:** TTL is lazy - expired items stick around in memory until you call `cleanup_expired()` or they get overwritten. For long-running apps, clean up periodically or you'll leak memory.
### Persistence
**Snapshots (default):** Point-in-time saves, good for edge devices
```rust
let config = Config::default().with_snapshot_auto_ops(1000);
let db = DBBuilder::new()
.snapshot_path("data.snapshot")
.config(config)
.build()?;
// Auto-saves every 1000 operations
```
**AOF (optional):** Write-ahead log, good for zero data loss
```rust
let db = DBBuilder::new()
.aof_path("data.aof")
.build()?;
// Requires --features aof
```
## Platforms
**Supported:**
- Linux (x86_64, aarch64)
- macOS (x86_64, arm64)
**Not supported:**
- Windows (use WSL2 or Docker)
See [PLATFORMS.md](PLATFORMS.md) for details.
## API Overview
**Key-Value:**
```rust
db.insert(key, value, options)?;
db.get(key)?;
db.delete(key)?;
```
**Spatial:**
```rust
db.insert_point(namespace, &point, data, options)?;
db.query_within_radius(namespace, ¢er, radius, limit)?;
db.count_within_radius(namespace, ¢er, radius)?;
db.contains_point(namespace, ¢er, radius)?;
db.find_within_bounds(namespace, min_lat, min_lon, max_lat, max_lon, limit)?;
db.knn(namespace, ¢er, k, max_radius, metric)?;
db.query_within_polygon(namespace, &polygon, limit)?;
db.distance_between(&p1, &p2, metric)?;
```
**3D Spatial:**
```rust
db.insert_point_3d(namespace, &point3d, data, options)?;
db.query_within_sphere_3d(namespace, ¢er, radius, limit)?;
db.query_within_cylinder_3d(namespace, ¢er, min_z, max_z, radius, limit)?;
db.query_within_bbox_3d(namespace, &bbox, limit)?;
db.knn_3d(namespace, ¢er, k)?;
```
**Trajectories:**
```rust
db.insert_trajectory(object_id, &points, options)?;
db.query_trajectory(object_id, start_time, end_time)?;
```
**Utility:**
```rust