Rust bindings for the GeoDESK C++ library using cxx.rs.
GeoDESK is a fast and efficient library for querying OpenStreetMap data from GOL (Geographic Object Library) files. This crate provides safe Rust bindings to the GeoDESK C++ API, allowing you to query OSM features using GOQL (Geographic Object Query Language).
- 🚀 Fast queries of OpenStreetMap data
- 🗺️ Support for GOQL (Geographic Object Query Language)
- 📦 Query features by type, tags, and bounding box
- 🔍 Built-in convenience methods for common queries (restaurants, roads, bus stops, etc.)
- 🦀 Safe Rust API with zero-cost abstractions
- 🔗 Direct bindings to C++ for maximum performance
This library automatically downloads and builds GeoDESK using CMake's FetchContent feature! You only need:
-
CMake 3.14 or higher
- Linux:
sudo apt install cmake(Debian/Ubuntu) orsudo yum install cmake(RHEL/CentOS) - macOS:
brew install cmake - Windows: Download from https://cmake.org/download/
- Linux:
-
C++20 compatible compiler
- GCC 10+, Clang 11+, or MSVC 2019+
-
A GOL file containing OpenStreetMap data
- Build using the
golcommand-line tool:gol build world planet-latest.osm.pbf
- Or download pre-built GOL files from GeoDESK
- Build using the
That's it! No need to manually install GeoDESK - it's fetched automatically during build.
Add this to your Cargo.toml:
[dependencies]
geodesk-rs = "0.1.0"The first build will take a few minutes as CMake downloads and compiles GeoDESK. Subsequent builds will be much faster.
use geodesk_rs::{GeoDesk, BoundingBox};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Open a GOL file
let geodesk = GeoDesk::open("world.gol")?;
// Define a bounding box for Montreal, Quebec
let bbox = BoundingBox::new(-73.9781, 45.4042, -73.4766, 45.7042);
// Query restaurants
let restaurants = geodesk.query_amenities("restaurant", bbox)?;
println!("Found {} restaurants", restaurants.count());
// Get results as a vector
let features = restaurants.to_vec()?;
for restaurant in features.iter().take(5) {
println!("- {} at ({:.4}, {:.4})",
restaurant.name, restaurant.lon, restaurant.lat);
}
Ok(())
}let bbox = BoundingBox::new(-73.9, 45.4, -73.5, 45.7);
// Query specific amenity types
let restaurants = geodesk.query_amenities("restaurant", bbox)?;
let cafes = geodesk.query_amenities("cafe", bbox)?;
let hotels = geodesk.query_amenities("hotel", bbox)?;
// Or use convenience methods
let restaurants = geodesk.query_restaurants(bbox)?;
let cafes = geodesk.query_cafes(bbox)?;
let bars = geodesk.query_bars(bbox)?;let copenhagen = BoundingBox::new(12.45, 55.61, 12.65, 55.73);
// Query all roads
let roads = geodesk.query_roads(copenhagen)?;
// Query bus stops
let bus_stops = geodesk.query_bus_stops(copenhagen)?;
// Query specific road types using GOQL
let motorways = geodesk.query("w[highway=motorway]", copenhagen)?;
let residential = geodesk.query("w[highway=residential]", copenhagen)?;GOQL (Geographic Object Query Language) allows powerful filtering:
// Multiple amenity types
let food = geodesk.query("na[amenity=restaurant,cafe,bar]", bbox)?;
// Parks
let parks = geodesk.query("a[leisure=park]", bbox)?;
// Schools
let schools = geodesk.query("na[amenity=school]", bbox)?;
// Water features
let water = geodesk.query("wa[natural=water,waterway]", bbox)?;
// Traffic signals
let signals = geodesk.query("n[highway=traffic_signals]", bbox)?;let results = geodesk.query_restaurants(bbox)?;
// Get count
println!("Found {} restaurants", results.count());
// Check if empty
if results.is_empty() {
println!("No results found");
}
// Convert to vector and iterate
let features = results.to_vec()?;
for feature in features {
println!("Name: {}", feature.name);
println!("Location: ({}, {})", feature.lon, feature.lat);
println!("Type: {}", feature.type_name);
// Access tags
if let Some(cuisine) = feature.tag("cuisine") {
println!("Cuisine: {}", cuisine);
}
// Check if tag exists
if feature.has_tag("wheelchair") {
println!("Wheelchair accessible");
}
}// Create from explicit coordinates (West, South, East, North)
let bbox = BoundingBox::new(-73.9, 45.4, -73.5, 45.7);
// Create from center point and radius
let bbox = BoundingBox::from_center(-73.7, 45.5, 0.2); // 0.2 degrees radiusGeoDESK uses GOQL (Geographic Object Query Language) for filtering features:
n- Nodes (points)w- Ways (lines, excluding areas)a- Areas (polygons)r- Relations (excluding areas)*- Any type
Types can be combined: na selects nodes and areas.
na[amenity=restaurant] # Exact match
w[highway] # Tag exists
w[highway][!oneway] # Tag doesn't exist or is not 'yes'
na[amenity=bar,pub,cafe] # Multiple values
w[highway=residential][name] # Multiple conditions
na[name=The*] # Wildcard match
na[population>=1000000] # Numeric comparison
na[amenity=restaurant] # Restaurants
w[highway] # All roads
na[tourism=hotel][stars>=4] # 4+ star hotels
w[highway=residential][!oneway] # Non-oneway residential streets
na[amenity=restaurant,cafe,bar] # Food & drink establishments
a[leisure=park][name] # Named parks
n[highway=traffic_signals] # Traffic lights
Main interface to GOL files.
open(path)- Open a GOL filequery(goql, bbox)- Execute a GOQL queryquery_amenities(type, bbox)- Query amenities by typequery_restaurants(bbox)- Query restaurantsquery_cafes(bbox)- Query cafesquery_bars(bbox)- Query bars and pubsquery_bus_stops(bbox)- Query bus stopsquery_roads(bbox)- Query roadsquery_all_amenities(bbox)- Query all amenities
Represents a geographic bounding box.
new(west, south, east, north)- Create from coordinatesfrom_center(lon, lat, radius)- Create from center and radius
Result of a query operation.
count()- Number of featuresis_empty()- Check if emptyto_vec()- Convert to vector of features
Represents an OSM feature.
id- OSM IDtype_name- Feature type ("node", "way", "relation")name- Name tag valuelon,lat- Coordinates (for ways/relations, the bbox midpoint)tags- All tags as key-value pairstag(key)- Get tag valuehas_tag(key)- Check if tag existsnodes- Way geometry as orderedNodes (empty for non-ways)is_area/is_area()-truefor area ways and area relationsrings- Polygon rings (empty for non-areas)outer_rings()/inner_rings()- Iterators over the filled rings / holesbounds- The feature's bounding box (BoundingBox, WGS-84). A degenerate box at the point for a node; the full extent for a way/relation — e.g. a city's extent when the feature is its administrative boundary. Computed from GeoDESK's native feature bbox (no geometry materialization).
Area features carry their polygon geometry in rings. This covers
neighbourhoods, suburbs and administrative boundaries — whether mapped as a
closed way (single outer ring) or as a multipolygon relation (member
ways are stitched into outer/inner rings via GeoDESK's Polygonizer).
use geodesk_rs::{GeoDesk, BoundingBox};
let geodesk = GeoDesk::open("world.gol")?;
let bbox = BoundingBox::new(-6.32, 53.32, -6.20, 53.38); // central Dublin
// Neighbourhoods, with their boundaries
let hoods = geodesk.query("na[place=neighbourhood,suburb,quarter]", bbox)?;
for f in hoods.to_vec()? {
if f.is_area() {
for ring in f.outer_rings() {
// ring.coords is a closed loop of { lon, lat } (first == last)
println!("{} boundary: {} vertices", f.name, ring.coords.len());
}
} else {
// Mapped as a point label — only a centroid is available
println!("{} at ({:.4}, {:.4})", f.name, f.lon, f.lat);
}
}Ring-{ is_outer: bool, coords: Vec<Coord> }; outer rings are the filled area, inner rings are holes. Coordinates are closed (first vertex repeated).Coord-{ lon: f64, lat: f64 }in WGS-84 degrees.
# Clone the repository
git clone https://github.com/yourusername/geodesk-rs
cd geodesk-rs
# Build (GeoDESK will be automatically fetched and built via CMake)
cargo build --release
# Run examples
cargo run --example query_restaurants
cargo run --example query_roads_copenhagen
cargo run --example custom_queriesNote: The first build will take several minutes as CMake downloads and compiles the GeoDESK C++ library automatically using FetchContent. Subsequent builds will be much faster as the library is cached.
The examples/ directory contains several examples:
query_restaurants.rs- Query restaurants in Montrealquery_roads_copenhagen.rs- Query roads and bus stops in Copenhagencustom_queries.rs- Various GOQL query examples
Run them with:
cargo run --example query_restaurantsGeoDESK is designed for high-performance queries:
- Queries are executed lazily
- Spatial indexing for fast bounding box lookups
- Zero-copy access to GOL data
- Efficient tag filtering
Make sure GeoDESK is installed and either:
- Set
GEODESK_PATHenvironment variable - Install GeoDESK headers to
/usr/local/include
Ensure the GeoDESK library is in your library path:
export LD_LIBRARY_PATH=$GEODESK_PATH/lib:$LD_LIBRARY_PATH # Linux
export DYLD_LIBRARY_PATH=$GEODESK_PATH/lib:$DYLD_LIBRARY_PATH # macOSContributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the same terms as the GeoDESK library.
- GeoDESK Website
- GeoDESK Documentation
- GeoDESK C++ API
- GOQL Reference
- OpenStreetMap
- cxx.rs Documentation
- GeoDESK C++ library by Clarisma
- OpenStreetMap contributors
- cxx.rs by David Tolnay