git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
11 KiB
Geospatial & Mapping API Clients - Implementation Summary
Overview
Created a comprehensive Rust client module for geospatial and mapping APIs, fully integrated with RuVector's semantic vector framework. The implementation follows TDD principles with strict rate limiting and proper error handling.
Files Created
1. Main Implementation
File: src/geospatial_clients.rs (1,250 lines)
Four complete async clients:
- ✅ NominatimClient - OpenStreetMap geocoding with STRICT 1 req/sec rate limiting
- ✅ OverpassClient - OSM data queries using Overpass QL
- ✅ GeonamesClient - Place name database (requires username)
- ✅ OpenElevationClient - Elevation data lookup
2. Demo Application
File: examples/geospatial_demo.rs (272 lines)
Comprehensive demonstration of all four clients with:
- Real API usage examples
- Error handling patterns
- Rate limiting demonstrations
- Geographic distance calculations
3. Documentation
File: docs/GEOSPATIAL_CLIENTS.md (547 lines)
Complete documentation including:
- API reference for all clients
- Usage examples
- Rate limiting guidelines
- Best practices
- Advanced usage patterns
- Cross-domain integration examples
4. Library Integration
Modified: src/lib.rs
Added module and re-exports:
pub mod geospatial_clients;
pub use geospatial_clients::{
GeonamesClient, NominatimClient,
OpenElevationClient, OverpassClient
};
Implementation Details
NominatimClient
API: https://nominatim.openstreetmap.org Rate Limit: 1 request/second (STRICTLY ENFORCED)
Features:
- Mutex-based rate limiter to ensure 1 req/sec compliance
- Required User-Agent header for OSM policy compliance
- Three main methods:
geocode(address)- Address to coordinatesreverse_geocode(lat, lon)- Coordinates to addresssearch(query, limit)- Place name search
Metadata captured:
place_id,osm_type,osm_idlatitude,longitudedisplay_name,place_type,importancecity,country,country_code
OverpassClient
API: https://overpass-api.de/api Rate Limit: ~2 requests/second (conservative)
Features:
- Custom Overpass QL query execution
- Built-in helpers for common queries:
get_nearby_pois(lat, lon, radius, amenity)- Find POIsget_roads(south, west, north, east)- Get road network
- Support for all OSM tags
Metadata captured:
osm_id,osm_typelatitude,longitudename,amenity,highway- All OSM tags as
osm_tag_*
GeonamesClient
API: http://api.geonames.org Rate Limit: ~0.5 requests/second (2000/hour free tier) Auth: Requires username from geonames.org
Features:
- Four main methods:
search(query, limit)- Place name searchget_nearby(lat, lon)- Nearby placesget_timezone(lat, lon)- Timezone lookupget_country_info(country_code)- Country details
Metadata captured:
geoname_id,name,toponym_namelatitude,longitudecountry_code,country_name,admin_name1feature_class,feature_codepopulation
OpenElevationClient
API: https://api.open-elevation.com/api/v1 Rate Limit: ~5 requests/second Auth: None required
Features:
- Two main methods:
get_elevation(lat, lon)- Single pointget_elevations(locations)- Batch lookup
- Uses SRTM data for worldwide coverage
Metadata captured:
latitude,longitudeelevation_m(meters above sea level)
Technical Architecture
Rate Limiting Strategy
Each client implements appropriate rate limiting:
// Nominatim: STRICT 1 req/sec with Mutex
last_request: Arc<Mutex<Option<Instant>>>
async fn enforce_rate_limit(&self) {
let mut last = self.last_request.lock().await;
if let Some(last_time) = *last {
let elapsed = last_time.elapsed();
if elapsed < self.rate_limit_delay {
sleep(self.rate_limit_delay - elapsed).await;
}
}
*last = Some(Instant::now());
}
// Other clients: Simple delay
sleep(self.rate_limit_delay).await;
SemanticVector Integration
All responses are converted to RuVector's SemanticVector format:
fn convert_*(&self, data) -> Result<Vec<SemanticVector>> {
let text = format!("..."); // Create searchable text
let embedding = self.embedder.embed_text(&text);
SemanticVector {
id: format!("SOURCE:{}", id),
embedding,
domain: Domain::CrossDomain,
timestamp: Utc::now(),
metadata, // Geographic metadata
}
}
Error Handling
All clients use the framework's error types:
async fn fetch_with_retry(&self, url: &str) -> Result<Response> {
let mut retries = 0;
loop {
match self.client.get(url).send().await {
Ok(response) => {
if response.status() == StatusCode::TOO_MANY_REQUESTS
&& retries < MAX_RETRIES {
retries += 1;
sleep(Duration::from_millis(RETRY_DELAY_MS * retries as u64)).await;
continue;
}
return Ok(response);
}
Err(_) if retries < MAX_RETRIES => {
retries += 1;
sleep(Duration::from_millis(RETRY_DELAY_MS * retries as u64)).await;
}
Err(e) => return Err(FrameworkError::Network(e)),
}
}
}
Testing
Test Coverage
Comprehensive test suite included:
-
Client Creation Tests
test_nominatim_client_creationtest_overpass_client_creationtest_geonames_client_creationtest_open_elevation_client_creation
-
Rate Limiting Tests
test_nominatim_rate_limiting- Verifies STRICT 1 sec enforcementtest_rate_limits- Validates all rate limit constants
-
Data Conversion Tests
test_nominatim_place_conversiontest_overpass_element_conversiontest_geonames_conversiontest_elevation_conversion
-
GeoUtils Integration Tests
test_geo_utils_integration- Distance calculationstest_geo_utils_within_radius- Radius checking
-
Compliance Tests
test_user_agent_constant- OSM User-Agent requirement
Running Tests
# All geospatial tests
cargo test geospatial
# Specific tests
cargo test nominatim
cargo test test_nominatim_rate_limiting
# Build verification
cargo build --lib
GeoUtils Integration
All clients leverage the existing GeoUtils from physics_clients.rs:
// Distance calculation (Haversine formula)
let distance = GeoUtils::distance_km(
lat1, lon1,
lat2, lon2
);
// Radius check
let within = GeoUtils::within_radius(
center_lat, center_lon,
point_lat, point_lon,
radius_km
);
Usage Examples
Basic Geocoding
let client = NominatimClient::new()?;
let results = client.geocode("Eiffel Tower, Paris").await?;
Finding Nearby POIs
let client = OverpassClient::new()?;
let cafes = client.get_nearby_pois(48.8584, 2.2945, 500.0, "cafe").await?;
Place Search
let client = GeonamesClient::new(username)?;
let results = client.search("Paris", 10).await?;
Elevation Lookup
let client = OpenElevationClient::new()?;
let elevation = client.get_elevation(27.9881, 86.9250).await?;
Cross-Domain Discovery
let mut engine = NativeDiscoveryEngine::new(config);
// Add geospatial data
for place in nominatim_results {
engine.add_vector(place);
}
// Add earthquake data
for eq in usgs_results {
engine.add_vector(eq);
}
// Detect patterns linking earthquakes to populated areas
let patterns = engine.detect_patterns();
API Compliance
OpenStreetMap Policy Compliance
✅ User-Agent: All OSM services include proper User-Agent
const USER_AGENT: &str = "RuVector-Data-Framework/1.0 (https://github.com/ruvnet/ruvector)";
✅ Rate Limiting: Nominatim strictly enforces 1 req/sec
const NOMINATIM_RATE_LIMIT_MS: u64 = 1000; // 1 second
✅ Attribution: OSM data usage properly attributed in metadata
metadata.insert("source".to_string(), "nominatim".to_string());
Service Limits
| Service | Free Tier Limit | Implementation |
|---|---|---|
| Nominatim | 1 req/sec | Strictly enforced with Mutex |
| Overpass | No hard limit | Conservative 2 req/sec |
| GeoNames | 2000/hour | Conservative 0.5 req/sec |
| OpenElevation | No hard limit | Light 5 req/sec delay |
Dependencies
All dependencies already present in workspace:
tokio = { workspace = true, features = ["full"] }
reqwest = { workspace = true }
serde = { workspace = true }
chrono = { workspace = true }
urlencoding = "2.1"
Build Status
✅ Compiles: All code compiles without errors ✅ Tests: All tests pass with mocked data ✅ Documentation: Complete API documentation ✅ Examples: Working demo application ✅ Integration: Fully integrated with lib.rs
$ cargo build --lib
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s
Code Metrics
| Component | Lines of Code |
|---|---|
| geospatial_clients.rs | 1,250 |
| geospatial_demo.rs | 272 |
| GEOSPATIAL_CLIENTS.md | 547 |
| Total | 2,069 |
Future Enhancements
Potential improvements for future development:
-
Additional Clients
- Google Maps API (requires API key)
- MapBox API (requires API key)
- Here Maps API (requires API key)
- OpenCage Geocoding API
-
Advanced Features
- Caching layer for frequent queries
- Batch processing optimization
- Polygon/bounding box support
- GeoJSON output format
- KML/KMZ export
-
Performance
- Connection pooling
- Request queuing
- Parallel batch processing (respecting rate limits)
- Response compression
-
Integration
- PostGIS database integration
- GeoParquet export
- Spatial indexing
- Vector tile generation
Conclusion
Successfully implemented a comprehensive geospatial client module with:
- ✅ 4 Complete Clients with full API coverage
- ✅ Strict Rate Limiting especially for OSM services
- ✅ SemanticVector Integration for RuVector discovery
- ✅ Comprehensive Tests with mock data
- ✅ Complete Documentation with examples
- ✅ Working Demo application
- ✅ OSM Policy Compliance with User-Agent and rate limits
- ✅ GeoUtils Integration for distance calculations
- ✅ Error Handling with retry logic
- ✅ Production Ready code quality
The implementation follows established patterns from physics_clients.rs and integrates seamlessly with RuVector's semantic vector framework, enabling cross-domain geographic discovery and analysis.