use arcgis::example_tracker::ExampleTracker;
use arcgis::{ArcGISClient, FeatureServiceClient, LayerId, NoAuth, ObjectId};
const WORLD_CITIES_SERVICE: &str =
"https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/World_Cities/FeatureServer";
#[tokio::main]
async fn main() -> anyhow::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("query_features")
.service_type("ExampleClient")
.start();
tracing::info!("🌍 Feature Service Query Examples");
tracing::info!("Using ESRI's public World Cities service");
let client = ArcGISClient::new(NoAuth);
let service = FeatureServiceClient::new(WORLD_CITIES_SERVICE, &client);
let layer_id = LayerId::new(0);
demonstrate_basic_where_query(&service, layer_id).await?;
demonstrate_field_filtering(&service, layer_id).await?;
demonstrate_count_only(&service, layer_id).await?;
demonstrate_object_id_query(&service, layer_id).await?;
demonstrate_manual_pagination(&service, layer_id).await?;
demonstrate_auto_pagination(&service, layer_id).await?;
demonstrate_alternative_formats(&service, layer_id).await?;
tracing::info!("\n✅ All query examples completed successfully!");
print_best_practices();
tracker.success();
Ok(())
}
async fn demonstrate_basic_where_query(
service: &FeatureServiceClient<'_>,
layer_id: LayerId,
) -> anyhow::Result<()> {
tracing::info!("\n=== Example 1: Basic WHERE Clause Query ===");
tracing::info!("Query cities with population > 5 million");
let result = service
.query(layer_id)
.where_clause("POP > 5000000")
.out_fields(&["CITY_NAME", "POP", "CNTRY_NAME"])
.return_geometry(true)
.limit(10)
.execute()
.await?;
assert!(
!result.features().is_empty(),
"Expected to find cities with population > 5 million"
);
tracing::info!(
feature_count = result.features().len(),
"Found large cities"
);
for feature in result.features().iter().take(3) {
let city = feature.attributes().get("CITY_NAME");
let pop = feature.attributes().get("POP");
let country = feature.attributes().get("CNTRY_NAME");
assert!(city.is_some(), "CITY_NAME field should be present");
assert!(pop.is_some(), "POP field should be present");
assert!(
feature.geometry().is_some(),
"Geometry should be present when return_geometry=true"
);
tracing::info!(
city = ?city,
population = ?pop,
country = ?country,
has_geometry = feature.geometry().is_some(),
"City details"
);
}
Ok(())
}
async fn demonstrate_field_filtering(
service: &FeatureServiceClient<'_>,
layer_id: LayerId,
) -> anyhow::Result<()> {
tracing::info!("\n=== Example 2: Field Filtering (No Geometry) ===");
tracing::info!("Query specific fields without geometry for faster response");
let result = service
.query(layer_id)
.where_clause("POP > 1000000")
.out_fields(&["CITY_NAME", "POP"])
.return_geometry(false) .limit(5)
.execute()
.await?;
assert!(
!result.features().is_empty(),
"Expected to find cities with population > 1 million"
);
tracing::info!(
feature_count = result.features().len(),
"Retrieved features (attributes only)"
);
for feature in result.features() {
assert!(
feature.geometry().is_none(),
"Geometry should be None when return_geometry=false"
);
assert!(
feature.attributes().get("CITY_NAME").is_some(),
"CITY_NAME should be present"
);
let has_geom = feature.geometry().is_some();
tracing::debug!(
has_geometry = has_geom,
attributes = ?feature.attributes(),
"Feature without geometry"
);
}
Ok(())
}
async fn demonstrate_count_only(
service: &FeatureServiceClient<'_>,
layer_id: LayerId,
) -> anyhow::Result<()> {
tracing::info!("\n=== Example 3: Count-Only Query ===");
tracing::info!("Get count of features without retrieving data");
let result = service
.query(layer_id)
.where_clause("POP > 100000")
.count_only(true)
.execute()
.await?;
assert!(
result.count().is_some(),
"Count should be returned when count_only=true"
);
if let Some(count) = result.count() {
assert!(*count > 0, "Expected at least some cities with pop > 100k");
tracing::info!(
count = count,
features_returned = result.features().len(),
"Cities with population > 100,000"
);
}
Ok(())
}
async fn demonstrate_object_id_query(
service: &FeatureServiceClient<'_>,
layer_id: LayerId,
) -> anyhow::Result<()> {
tracing::info!("\n=== Example 4: Query by Object IDs ===");
tracing::info!("Retrieve specific features by their ObjectID");
let result = service
.query(layer_id)
.object_ids(&[ObjectId::new(1), ObjectId::new(2), ObjectId::new(3)])
.out_fields(&["*"]) .execute()
.await?;
assert!(
!result.features().is_empty(),
"Expected features for ObjectIDs 1, 2, 3"
);
tracing::info!(
requested = 3,
returned = result.features().len(),
"Retrieved features by ObjectID"
);
Ok(())
}
async fn demonstrate_manual_pagination(
service: &FeatureServiceClient<'_>,
layer_id: LayerId,
) -> anyhow::Result<()> {
tracing::info!("\n=== Example 5: Manual Pagination ===");
tracing::info!("Fetch results in pages using offset/limit");
let page_size = 5;
let mut total_fetched = 0;
for page in 0..3 {
let offset = page * page_size;
tracing::debug!(page = page, offset = offset, "Fetching page");
let result = service
.query(layer_id)
.where_clause("POP > 500000")
.out_fields(&["CITY_NAME", "POP"])
.return_geometry(false)
.limit(page_size)
.offset(offset)
.execute()
.await?;
let count = result.features().len();
total_fetched += count;
tracing::info!(
page = page,
features_in_page = count,
total_so_far = total_fetched,
"Page fetched"
);
if count < page_size as usize {
tracing::debug!("Reached last page");
break;
}
}
assert!(
total_fetched > 0,
"Pagination should have returned at least some features"
);
Ok(())
}
async fn demonstrate_auto_pagination(
service: &FeatureServiceClient<'_>,
layer_id: LayerId,
) -> anyhow::Result<()> {
tracing::info!("\n=== Example 6: Auto-Pagination ===");
tracing::info!("Let the SDK handle pagination automatically");
let result = service
.query(layer_id)
.where_clause("POP > 200000")
.out_fields(&["CITY_NAME", "POP"])
.return_geometry(false)
.limit(5) .execute_all() .await?;
tracing::info!(
total_features = result.features().len(),
exceeded_limit = result.exceeded_transfer_limit(),
"Auto-pagination completed"
);
Ok(())
}
async fn demonstrate_alternative_formats(
service: &FeatureServiceClient<'_>,
layer_id: LayerId,
) -> anyhow::Result<()> {
tracing::info!("\n=== Example 7: Alternative Response Formats ===");
tracing::info!("Testing GeoJSON and PBF format support");
let geojson_result = service
.query(layer_id)
.where_clause("POP > 5000000")
.out_fields(&["CITY_NAME", "POP"])
.return_geometry(false)
.limit(3)
.geojson()
.execute()
.await?;
tracing::info!(
feature_count = geojson_result.features().len(),
"GeoJSON query completed"
);
for feature in geojson_result.features() {
let city = feature.attributes().get("CITY_NAME");
let pop = feature.attributes().get("POP");
tracing::info!(city = ?city, population = ?pop, "City from GeoJSON");
}
let pbf_result = service
.query(layer_id)
.where_clause("POP > 5000000")
.out_fields(&["CITY_NAME", "POP"])
.return_geometry(false)
.limit(3)
.pbf()
.execute()
.await?;
tracing::info!(
feature_count = pbf_result.features().len(),
"PBF query completed"
);
for feature in pbf_result.features() {
let city = feature.attributes().get("CITY_NAME");
let pop = feature.attributes().get("POP");
tracing::info!(city = ?city, population = ?pop, "City from PBF");
}
Ok(())
}
fn print_best_practices() {
tracing::info!("\n💡 Query Best Practices:");
tracing::info!(
" - Use return_geometry(false) for better performance when you don't need geometry"
);
tracing::info!(" - Use count_only(true) to get counts without retrieving features");
tracing::info!(" - Use execute_all() for automatic pagination");
tracing::info!(" - Narrow queries with specific WHERE clauses to reduce data transfer");
tracing::info!(" - Request only needed fields with out_fields() instead of all fields");
tracing::info!("");
tracing::info!("🎯 Format Selection:");
tracing::info!(" - Default JSON: Universal compatibility, moderate performance");
tracing::info!(" - GeoJSON: Standard for web mapping libraries (Leaflet, Mapbox, etc.)");
tracing::info!(" - PBF: 3-5x faster for large datasets, binary format");
tracing::info!("");
tracing::info!("⚡ Performance Tips:");
tracing::info!(" - Skip geometry when doing attribute-only analysis");
tracing::info!(" - Use count_only for checking dataset size before full query");
tracing::info!(" - Paginate large result sets instead of fetching all at once");
tracing::info!(" - Consider spatial queries (bounding box) to limit results geographically");
}