Skip to content

Commit 8aac74c

Browse files
committed
UniFFI
1 parent 62de691 commit 8aac74c

9 files changed

Lines changed: 2611 additions & 1 deletion

File tree

Cargo.toml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,27 @@ libc = "0.2.174"
1616
geo = "0.31.0"
1717
geo-types = "0.7.13"
1818
num-traits = "0.2.19"
19+
uniffi = "0.28"
20+
thiserror = "2.0"
21+
uniffi_bindgen = { version = "0.28", optional = true }
22+
camino = { version = "1.1", optional = true }
1923

2024
[dev-dependencies]
2125
criterion = "0.7"
26+
uniffi_bindgen = "0.28"
2227

2328
[build-dependencies]
2429
cbindgen = "0.29.0"
30+
uniffi = { version = "0.28", features = ["build", "bindgen"] }
2531

2632
[features]
2733
headers = []
34+
python-bindings = []
35+
bindgen = ["uniffi_bindgen", "camino"]
2836

2937
[lib]
3038
name = "rdp"
31-
crate-type = ["cdylib"]
39+
crate-type = ["cdylib", "lib"]
3240
test = true
3341
doctest = false
3442
doc = true

build.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ fn write_headers() {
1212
}
1313

1414
fn main() {
15+
// Generate UniFFI scaffolding
16+
uniffi::generate_scaffolding("src/rdp.udl").unwrap();
17+
18+
// Note: Python bindings generation happens at runtime
19+
// Users can generate them manually using:
20+
// cargo run --bin uniffi-bindgen generate src/rdp.udl --language python --out-dir python
21+
22+
// Keep existing cbindgen for backward compatibility
1523
let headers_enabled = env::var_os("CARGO_FEATURE_HEADERS").is_some();
1624
if headers_enabled {
1725
write_headers();

examples/uniffi_example.py

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#!/usr/bin/env python
2+
"""
3+
uniffi_example.py
4+
5+
Example of using the UniFFI-generated Python bindings for the RDP library.
6+
This demonstrates the simplified interface compared to the manual ctypes FFI.
7+
8+
To run:
9+
1. Build the library with Python bindings: cargo build --release --features python-bindings
10+
2. Ensure the generated Python module is in your path
11+
3. Run this script: python examples/uniffi_example.py
12+
"""
13+
14+
import os
15+
import sys
16+
from enum import Enum
17+
18+
import numpy as np
19+
20+
# Add the python directory to the path to import the generated bindings
21+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "python"))
22+
23+
try:
24+
# Import the UniFFI generated module
25+
import rdp
26+
except ImportError:
27+
print(
28+
"Could not import rdp module. Make sure to build with: cargo build --release --features python-bindings"
29+
)
30+
sys.exit(1)
31+
32+
33+
class Algorithm(Enum):
34+
"""Simplification algorithms available."""
35+
36+
RDP = "rdp" # Ramer-Douglas-Peucker
37+
VW = "vw" # Visvalingam-Whyatt
38+
VWP = "vwp" # Visvalingam-Whyatt with topology preservation
39+
40+
41+
def simplify_linestring(coords, precision, algorithm=Algorithm.RDP):
42+
"""
43+
Simplify a linestring using the specified algorithm.
44+
45+
Args:
46+
coords: numpy array of shape (n, 2) or list of [x, y] pairs
47+
precision: simplification tolerance
48+
algorithm: Algorithm enum value
49+
50+
Returns:
51+
Simplified coordinates as numpy array
52+
"""
53+
# Convert to flat list efficiently
54+
flat = np.asarray(coords, dtype=np.float64).flatten().tolist()
55+
56+
# Map algorithm to corresponding function
57+
algorithm_map = {
58+
Algorithm.RDP: rdp.simplify_rdp,
59+
Algorithm.VW: rdp.simplify_visvalingam,
60+
Algorithm.VWP: rdp.simplify_visvalingam_preserve,
61+
}
62+
63+
try:
64+
simplify_func = algorithm_map[algorithm]
65+
result = simplify_func(flat, precision)
66+
except KeyError:
67+
raise ValueError(f"Unknown algorithm: {algorithm}")
68+
except rdp.RdpError as e:
69+
print(f"Error during simplification: {e}")
70+
raise
71+
72+
# Reshape back to (n, 2)
73+
return np.array(result).reshape(-1, 2)
74+
75+
76+
def simplify_indices(coords, precision, algorithm=Algorithm.RDP):
77+
"""
78+
Get indices of points to keep after simplification.
79+
80+
Args:
81+
coords: numpy array of shape (n, 2) or list of [x, y] pairs
82+
precision: simplification tolerance
83+
algorithm: Algorithm enum value (RDP or VW only)
84+
85+
Returns:
86+
Indices of points to keep
87+
"""
88+
# Convert to flat list
89+
flat = np.asarray(coords, dtype=np.float64).flatten().tolist()
90+
91+
# Map algorithm to corresponding function
92+
algorithm_map = {
93+
Algorithm.RDP: rdp.simplify_rdp_idx,
94+
Algorithm.VW: rdp.simplify_visvalingam_idx,
95+
}
96+
97+
try:
98+
simplify_func = algorithm_map[algorithm]
99+
result = simplify_func(flat, precision)
100+
except KeyError:
101+
raise ValueError(f"Algorithm {algorithm} does not support index output")
102+
except rdp.RdpError as e:
103+
print(f"Error during simplification: {e}")
104+
raise
105+
106+
return np.array(result, dtype=np.uint64)
107+
108+
109+
def main():
110+
# Example 1: RDP simplification
111+
print("Example 1: Ramer-Douglas-Peucker Simplification")
112+
print("-" * 50)
113+
114+
coords_rdp = np.array(
115+
[[0.0, 0.0], [5.0, 4.0], [11.0, 5.5], [17.3, 3.2], [27.8, 0.1]]
116+
)
117+
118+
print(f"Original points ({len(coords_rdp)} points):")
119+
print(coords_rdp)
120+
121+
simplified_rdp = simplify_linestring(coords_rdp, 1.0, Algorithm.RDP)
122+
print(f"\nSimplified with RDP (tolerance=1.0, {len(simplified_rdp)} points):")
123+
print(simplified_rdp)
124+
125+
indices_rdp = simplify_indices(coords_rdp, 1.0, Algorithm.RDP)
126+
print(f"\nIndices of kept points: {indices_rdp}")
127+
128+
# Example 2: Visvalingam-Whyatt simplification
129+
print("\n\nExample 2: Visvalingam-Whyatt Simplification")
130+
print("-" * 50)
131+
132+
coords_vw = np.array(
133+
[[5.0, 2.0], [3.0, 8.0], [6.0, 20.0], [7.0, 25.0], [10.0, 10.0]]
134+
)
135+
136+
print(f"Original points ({len(coords_vw)} points):")
137+
print(coords_vw)
138+
139+
simplified_vw = simplify_linestring(coords_vw, 30.0, Algorithm.VW)
140+
print(f"\nSimplified with VW (epsilon=30.0, {len(simplified_vw)} points):")
141+
print(simplified_vw)
142+
143+
indices_vw = simplify_indices(coords_vw, 30.0, Algorithm.VW)
144+
print(f"\nIndices of kept points: {indices_vw}")
145+
146+
# Example 3: Topology-preserving Visvalingam-Whyatt
147+
print("\n\nExample 3: Topology-Preserving Visvalingam-Whyatt")
148+
print("-" * 50)
149+
150+
simplified_vwp = simplify_linestring(coords_vw, 30.0, Algorithm.VWP)
151+
print(f"Simplified with VWP (epsilon=30.0, {len(simplified_vwp)} points):")
152+
print(simplified_vwp)
153+
154+
# Example 4: Error handling
155+
print("\n\nExample 4: Error Handling")
156+
print("-" * 50)
157+
158+
try:
159+
# Try with invalid input (odd number of coordinates)
160+
invalid_coords = [1.0, 2.0, 3.0]
161+
rdp.simplify_rdp(invalid_coords, 1.0)
162+
except (rdp.RdpError, rdp.InternalError) as e:
163+
print(f"Caught expected error for invalid input: {e}")
164+
165+
try:
166+
# Try with empty input
167+
empty_coords = []
168+
rdp.simplify_rdp(empty_coords, 1.0)
169+
except (rdp.RdpError, rdp.InternalError) as e:
170+
print(f"Caught expected error for empty input: {e}")
171+
172+
print('"All examples completed successfully!')
173+
174+
175+
if __name__ == "__main__":
176+
main()

generate_bindings.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env rust-script
2+
//! Generate Python bindings for the RDP library
3+
//!
4+
//! To run: rustc generate_bindings.rs && ./generate_bindings
5+
6+
use std::path::PathBuf;
7+
8+
fn main() {
9+
println!("Generating Python bindings...");
10+
11+
let udl_file = PathBuf::from("src/rdp.udl");
12+
let out_dir = PathBuf::from("python");
13+
14+
// Create output directory
15+
std::fs::create_dir_all(&out_dir).expect("Failed to create python directory");
16+
17+
// Generate Python bindings
18+
uniffi_bindgen::generate_bindings(
19+
&udl_file,
20+
None,
21+
vec![uniffi_bindgen::bindings::TargetLanguage::Python],
22+
Some(&out_dir),
23+
None,
24+
&uniffi_bindgen::Config::default(),
25+
false,
26+
).expect("Failed to generate Python bindings");
27+
28+
println!("Python bindings generated in python/ directory");
29+
}

0 commit comments

Comments
 (0)