use anyhow::Result;
use arcgis::example_tracker::ExampleTracker;
use arcgis::{
ArcGISClient, ArcGISGeometry, ArcGISPoint, ArcGISPolyline, GeometryServiceClient, LinearUnit,
NoAuth,
};
const GEOMETRY_SERVICE: &str =
"https://utility.arcgisonline.com/arcgis/rest/services/Geometry/GeometryServer";
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
)
.init();
let tracker = ExampleTracker::new("geometry_operations")
.service_type("ExampleClient")
.start();
tracing::info!("📐 ArcGIS Geometry Service Examples");
tracing::info!("Demonstrating spatial analysis and transformations");
let auth = NoAuth;
let client = ArcGISClient::new(auth);
let geom_service = GeometryServiceClient::new(GEOMETRY_SERVICE, &client);
demonstrate_coordinate_projection(&geom_service).await?;
demonstrate_buffer_creation(&geom_service).await?;
demonstrate_distance_calculation(&geom_service).await?;
demonstrate_batch_projection(&geom_service).await?;
demonstrate_line_length(&geom_service).await?;
tracing::info!("\n✅ All geometry operations completed successfully!");
print_best_practices();
tracker.success();
Ok(())
}
async fn demonstrate_coordinate_projection(geom_service: &GeometryServiceClient<'_>) -> Result<()> {
tracing::info!("\n=== Example 1: Coordinate Projection ===");
tracing::info!("Project coordinates from WGS84 (4326) to Web Mercator (3857)");
let sf_point = ArcGISPoint::new(
-122.4194, 37.7749, );
tracing::info!(
lon = *sf_point.x(),
lat = *sf_point.y(),
"Original coordinates (WGS84)"
);
let result = geom_service
.project(
vec![ArcGISGeometry::Point(sf_point.clone())],
4326, 3857, )
.await?;
assert!(
!result.geometries().is_empty(),
"No geometries in projection result"
);
if let Some(ArcGISGeometry::Point(projected)) = result.geometries().first() {
assert_ne!(
*projected.x(),
*sf_point.x(),
"X coordinate unchanged after projection"
);
assert_ne!(
*projected.y(),
*sf_point.y(),
"Y coordinate unchanged after projection"
);
assert!(
projected.x().abs() > 10_000.0,
"Web Mercator X coordinate seems incorrect: {}",
projected.x()
);
assert!(
projected.y().abs() > 1_000_000.0,
"Web Mercator Y coordinate seems incorrect: {}",
projected.y()
);
tracing::info!(
x = *projected.x(),
y = *projected.y(),
"✅ Projected coordinates (Web Mercator)"
);
}
Ok(())
}
async fn demonstrate_buffer_creation(geom_service: &GeometryServiceClient<'_>) -> Result<()> {
tracing::info!("\n=== Example 2: Buffer Creation ===");
tracing::info!("Create a 1000-meter buffer around a point");
let buffer_point = ArcGISPoint::new(-122.4194, 37.7749);
let buffer_params = arcgis::BufferParameters::builder()
.geometries(vec![ArcGISGeometry::Point(buffer_point)])
.in_sr(4326) .distances(vec![1000.0]) .unit(LinearUnit::Meters)
.union_results(false)
.geodesic(true) .build()
.map_err(|e| anyhow::anyhow!("Failed to build buffer parameters: {}", e))?;
tracing::debug!("Creating 1000m geodesic buffer");
let buffer_result = geom_service.buffer(buffer_params).await?;
assert!(
!buffer_result.geometries().is_empty(),
"No buffer geometries returned"
);
assert_eq!(
buffer_result.geometries().len(),
1,
"Expected 1 buffer polygon, got {}",
buffer_result.geometries().len()
);
tracing::info!(
buffer_count = buffer_result.geometries().len(),
"✅ Buffer polygons created"
);
Ok(())
}
async fn demonstrate_distance_calculation(geom_service: &GeometryServiceClient<'_>) -> Result<()> {
tracing::info!("\n=== Example 3: Distance Calculation ===");
tracing::info!("Calculate geodesic distance between San Francisco and Los Angeles");
let sf = ArcGISPoint::new(-122.4194, 37.7749);
let la = ArcGISPoint::new(-118.2437, 34.0522);
let distance_params = arcgis::DistanceParameters::builder()
.sr(4326) .geometry1(ArcGISGeometry::Point(sf))
.geometry2(ArcGISGeometry::Point(la))
.distance_unit(LinearUnit::Meters)
.geodesic(true) .build()
.map_err(|e| anyhow::anyhow!("Failed to build distance parameters: {}", e))?;
tracing::debug!("Calculating geodesic distance between SF and LA");
let distance_result = geom_service.distance(distance_params).await?;
let distance_km = distance_result.distance() / 1000.0;
let distance_mi = distance_km * 0.621371;
assert!(
distance_km > 500.0 && distance_km < 600.0,
"Distance out of expected range (500-600 km): {:.1} km",
distance_km
);
tracing::info!(
distance_meters = distance_result.distance(),
distance_km = format!("{:.2}", distance_km),
distance_miles = format!("{:.2}", distance_mi),
"✅ Distance calculated"
);
Ok(())
}
async fn demonstrate_batch_projection(geom_service: &GeometryServiceClient<'_>) -> Result<()> {
tracing::info!("\n=== Example 4: Batch Projection ===");
tracing::info!("Project multiple cities at once (more efficient than individual calls)");
let cities = [
("San Francisco", -122.4194, 37.7749),
("Los Angeles", -118.2437, 34.0522),
("Seattle", -122.3321, 47.6062),
("Portland", -122.6765, 45.5231),
];
let points: Vec<ArcGISGeometry> = cities
.iter()
.map(|(_, lon, lat)| ArcGISGeometry::Point(ArcGISPoint::new(*lon, *lat)))
.collect();
tracing::info!(point_count = points.len(), "Projecting cities in batch");
let batch_result = geom_service
.project(
points, 4326, 3857, )
.await?;
assert_eq!(
batch_result.geometries().len(),
cities.len(),
"Expected {} projected geometries, got {}",
cities.len(),
batch_result.geometries().len()
);
tracing::info!("✅ Batch projection completed");
for (i, (city_name, _, _)) in cities.iter().enumerate() {
if let Some(ArcGISGeometry::Point(projected)) = batch_result.geometries().get(i) {
tracing::debug!(
city = %city_name,
x_web_mercator = format!("{:.2}", projected.x()),
y_web_mercator = format!("{:.2}", projected.y()),
"Projected city"
);
}
}
Ok(())
}
async fn demonstrate_line_length(geom_service: &GeometryServiceClient<'_>) -> Result<()> {
tracing::info!("\n=== Example 5: Polyline Creation ===");
tracing::info!("Create and project a route line between cities");
let route_line = ArcGISPolyline::new(vec![vec![
vec![-122.4194, 37.7749], vec![-118.2437, 34.0522], ]]);
tracing::debug!("Creating route geometry");
let projected_route = geom_service
.project(
vec![ArcGISGeometry::Polyline(route_line)],
4326, 3857, )
.await?;
assert!(
!projected_route.geometries().is_empty(),
"No geometries in polyline projection result"
);
if let Some(ArcGISGeometry::Polyline(line)) = projected_route.geometries().first() {
assert!(!line.paths().is_empty(), "Polyline has no paths");
assert_eq!(line.paths().len(), 1, "Expected 1 path in polyline");
let first_path = line.paths().first().expect("Path should exist");
assert_eq!(first_path.len(), 2, "Expected 2 points in path (SF and LA)");
tracing::info!(
paths = line.paths().len(),
points_in_first_path = line.paths().first().map(|p| p.len()).unwrap_or(0),
"✅ Route line created and projected"
);
}
Ok(())
}
fn print_best_practices() {
tracing::info!("\n💡 Geometry Operations Best Practices:");
tracing::info!(" - Use geodesic=true for accurate Earth-surface distances and buffers");
tracing::info!(" - Batch operations are more efficient than individual calls");
tracing::info!(" - Project to appropriate coordinate systems for your use case");
tracing::info!(" - Use builder patterns for type-safe parameter construction");
tracing::info!(" - The Geometry Service is free and requires no authentication");
tracing::info!("");
tracing::info!("📍 Common Spatial Reference Systems:");
tracing::info!(" - WKID 4326 (WGS84): Standard GPS coordinates (lat/lon)");
tracing::info!(" - WKID 3857 (Web Mercator): Most web maps (Google, Bing, OSM)");
tracing::info!(" - WKID 4269 (NAD83): North American maps");
tracing::info!(" - WKID 2163 (US National Atlas): Equal area for US statistics");
tracing::info!("");
tracing::info!("⚡ Performance Optimization:");
tracing::info!(" - Project once, use many times (cache projected coordinates)");
tracing::info!(" - Batch geometries when possible (single API call)");
tracing::info!(" - Use appropriate spatial reference for operations:");
tracing::info!(" • WGS84 (4326) for distance/buffer with geodesic=true");
tracing::info!(" • Projected (3857, UTM) for planar calculations");
tracing::info!("");
tracing::info!("🎯 When to Use Each Operation:");
tracing::info!(" - project(): Display coordinates on maps, convert GPS to screen coords");
tracing::info!(" - buffer(): Proximity analysis, service areas, impact zones");
tracing::info!(" - distance(): Straight-line distances, as-the-crow-flies measurements");
tracing::info!(" - Note: Use routing services for actual travel distances on roads");
}