use anyhow::Result;
use arcgis::{
ArcGISClient, ExportTarget, GeometryType, IdentifyParams, ImageFormat, LayerSelection,
MapServiceClient, NoAuth,
};
const USA_MAP_SERVER: &str =
"https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer";
#[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();
tracing::info!("πΊοΈ Map Service Examples");
tracing::info!("Using ESRI's public USA MapServer service");
let client = ArcGISClient::new(NoAuth);
let map_service = MapServiceClient::new(USA_MAP_SERVER, &client);
demonstrate_basic_map_export(&map_service).await?;
demonstrate_transparent_export(&map_service).await?;
demonstrate_high_dpi_export(&map_service).await?;
demonstrate_identify_features(&map_service).await?;
demonstrate_find_by_text(&map_service).await?;
demonstrate_legend_retrieval(&map_service).await?;
tracing::info!("\nβ
All map service examples completed successfully!");
print_best_practices();
Ok(())
}
async fn demonstrate_basic_map_export(service: &MapServiceClient<'_>) -> Result<()> {
tracing::info!("\n=== Example 1: Basic Map Export ===");
tracing::info!("Export map of San Francisco Bay Area");
let sf_extent = "-122.5,37.6,-122.3,37.9";
let result = service
.export()
.bbox(sf_extent)
.size(800, 600)
.format(ImageFormat::Png)
.execute(ExportTarget::to_path("map_basic.png"))
.await?;
if let Some(path) = result.path() {
tracing::info!(
path = %path.display(),
"β
Map exported successfully"
);
tracing::info!(" Extent: San Francisco Bay Area");
tracing::info!(" Size: 800x600 pixels");
tracing::info!(" Format: PNG");
}
Ok(())
}
async fn demonstrate_transparent_export(service: &MapServiceClient<'_>) -> Result<()> {
tracing::info!("\n=== Example 2: Transparent Background Export ===");
tracing::info!("Export map with transparency for overlay use");
let la_extent = "-118.3,34.0,-118.2,34.1";
let result = service
.export()
.bbox(la_extent)
.size(1024, 768)
.format(ImageFormat::Png32) .transparent(true) .execute(ExportTarget::to_path("map_transparent.png"))
.await?;
if let Some(path) = result.path() {
tracing::info!(
path = %path.display(),
"β
Transparent map exported"
);
tracing::info!(" Background: Transparent (alpha channel)");
tracing::info!(" Use case: Perfect for overlaying on other imagery");
}
Ok(())
}
async fn demonstrate_high_dpi_export(service: &MapServiceClient<'_>) -> Result<()> {
tracing::info!("\n=== Example 3: High DPI Export ===");
tracing::info!("Export high-resolution map for print");
let seattle_extent = "-122.35,47.58,-122.30,47.63";
let result = service
.export()
.bbox(seattle_extent)
.size(1600, 1200) .format(ImageFormat::Png32)
.dpi(300) .execute(ExportTarget::to_path("map_high_dpi.png"))
.await?;
if let Some(path) = result.path() {
tracing::info!(
path = %path.display(),
"β
High-DPI map exported"
);
tracing::info!(" Resolution: 300 DPI (print quality)");
tracing::info!(" Size: 1600x1200 pixels");
tracing::info!(" Use case: Professional printing, large displays");
}
Ok(())
}
async fn demonstrate_identify_features(service: &MapServiceClient<'_>) -> Result<()> {
tracing::info!("\n=== Example 4: Identify Features at Point ===");
tracing::info!("Identify features at clicked location (Los Angeles area)");
let point_x = -118.25;
let point_y = 34.05;
let geometry = format!("{{\"x\":{},\"y\":{}}}", point_x, point_y);
let map_extent = "-120.0,32.0,-116.0,36.0";
let image_display = "800,600,96";
let params = IdentifyParams::builder()
.geometry(geometry)
.geometry_type(GeometryType::Point)
.map_extent(map_extent.to_string())
.image_display(image_display.to_string())
.layers(LayerSelection::Visible) .tolerance(5) .return_geometry(true)
.build()
.expect("Valid identify params");
let response = service.identify(params).await?;
tracing::info!(
result_count = response.results().len(),
"β
Identify completed"
);
if response.results().is_empty() {
tracing::info!(" No features found at this location");
tracing::info!(" Try different coordinates or increase tolerance");
} else {
tracing::info!("π Features identified:");
for (i, result) in response.results().iter().take(5).enumerate() {
tracing::info!(
" {}. Layer {}: {}",
i + 1,
result.layer_id(),
result.layer_name()
);
if !result.attributes().is_empty() {
let attrs: Vec<String> = result
.attributes()
.iter()
.take(3)
.map(|(k, v)| format!("{}: {:?}", k, v))
.collect();
tracing::info!(" Attributes: {}", attrs.join(", "));
}
}
}
Ok(())
}
async fn demonstrate_find_by_text(service: &MapServiceClient<'_>) -> Result<()> {
tracing::info!("\n=== Example 5: Find Features by Text ===");
tracing::info!("Search for cities containing 'Los' in their name");
let params = arcgis::FindParams::builder()
.search_text("Los")
.layers(vec![0]) .search_fields(vec!["AREANAME".to_string()]) .sr(4326) .contains(true) .return_geometry(false) .build()
.expect("Valid find params");
let response = service.find(params).await?;
tracing::info!(result_count = response.results().len(), "β
Find completed");
if response.results().is_empty() {
tracing::info!(" No cities found containing 'Los'");
} else {
tracing::info!("π Cities found:");
for (i, result) in response.results().iter().take(5).enumerate() {
tracing::info!(
" {}. Layer {}: {}",
i + 1,
result.layer_id(),
result.layer_name()
);
if !result.found_field_name().is_empty() {
tracing::info!(
" Match in field '{}': {}",
result.found_field_name(),
result.value()
);
}
}
if response.results().len() > 5 {
tracing::info!(" ... and {} more results", response.results().len() - 5);
}
}
Ok(())
}
async fn demonstrate_legend_retrieval(service: &MapServiceClient<'_>) -> Result<()> {
tracing::info!("\n=== Example 6: Legend Retrieval ===");
tracing::info!("Get legend symbols and labels for all layers");
let legend = service.get_legend().await?;
tracing::info!(layer_count = legend.layers().len(), "β
Legend retrieved");
tracing::info!("π¨ Legend information:");
for layer in legend.layers().iter().take(5) {
tracing::info!(" Layer {}: {}", layer.layer_id(), layer.layer_name());
if !layer.legend().is_empty() {
tracing::info!(" Symbols:");
for (i, symbol) in layer.legend().iter().take(3).enumerate() {
tracing::info!(" {}. {}", i + 1, symbol.label());
if let Some(url) = symbol.url() {
tracing::debug!(" Icon URL: {}", url);
}
}
if layer.legend().len() > 3 {
tracing::info!(" ... and {} more symbols", layer.legend().len() - 3);
}
}
}
if legend.layers().len() > 5 {
tracing::info!(" ... and {} more layers", legend.layers().len() - 5);
}
Ok(())
}
fn print_best_practices() {
tracing::info!("\nπ‘ Map Service Best Practices:");
tracing::info!(" - Use appropriate image formats (PNG for transparency, JPG for photos)");
tracing::info!(" - Request only the extent you need to minimize data transfer");
tracing::info!(" - Use DPI setting (96 for screen, 300 for print)");
tracing::info!(" - Cache exported maps when extent/params don't change");
tracing::info!(" - Set appropriate tolerance for identify operations");
tracing::info!("");
tracing::info!("π― Format Selection:");
tracing::info!(" - PNG32: Transparency support, best for overlays");
tracing::info!(" - PNG24: No transparency, smaller than PNG32");
tracing::info!(" - JPG: Smallest file size, no transparency, best for photos");
tracing::info!(" - PDF/SVG: Vector formats for scalable graphics");
tracing::info!("");
tracing::info!("β‘ Performance Tips:");
tracing::info!(" - Stream large exports to files instead of loading into memory");
tracing::info!(" - Use cached tile services when available (export_tile)");
tracing::info!(" - Limit identify tolerance to reduce processing time");
tracing::info!(" - Use contains=true for partial text matching in find operations");
tracing::info!("");
tracing::info!("π Extent Format:");
tracing::info!(" - Bbox format: 'xmin,ymin,xmax,ymax' (west,south,east,north)");
tracing::info!(" - Use WGS84 (EPSG:4326) for lat/lon coordinates");
tracing::info!(" - Use Web Mercator (EPSG:3857) for web mapping");
tracing::info!(" - Match service spatial reference for best results");
}