use anyhow::Result;
use arcgis::example_tracker::ExampleTracker;
use arcgis::{
ArcGISClient, FontStack, GlyphRange, NoAuth, TileCoordinate, VectorTileServiceClient,
};
use std::collections::HashMap;
const WORLD_BASEMAP: &str =
"https://basemaps.arcgis.com/arcgis/rest/services/World_Basemap_v2/VectorTileServer";
#[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("vector_tiles")
.service_type("ExampleClient")
.start();
tracing::info!("πΊοΈ Vector Tile Service Examples");
tracing::info!("Using ArcGIS World Basemap (public, no auth required)");
tracing::info!("");
let client = ArcGISClient::new(NoAuth);
let vt_service = VectorTileServiceClient::new(WORLD_BASEMAP, &client);
demonstrate_style_retrieval(&vt_service).await?;
demonstrate_single_tile(&vt_service).await?;
demonstrate_batch_tiles(&vt_service).await?;
demonstrate_font_glyphs(&vt_service).await?;
demonstrate_sprite_resources(&vt_service).await?;
tracing::info!("\nβ
All vector tile examples completed successfully!");
print_best_practices();
tracker.success();
Ok(())
}
async fn demonstrate_style_retrieval(vt_service: &VectorTileServiceClient<'_>) -> Result<()> {
tracing::info!("\n=== Example 1: Style Document Retrieval ===");
tracing::info!("Get Mapbox GL style JSON for rendering configuration");
tracing::info!("");
let style = vt_service.get_style().await?;
assert!(
*style.version() > 0,
"Style should have a valid Mapbox GL version, got {}",
style.version()
);
assert!(
!style.layers().is_empty(),
"Style should have at least one layer"
);
tracing::info!("β
Retrieved vector tile style");
tracing::info!(" Mapbox GL version: {}", style.version());
tracing::info!(" Layer count: {}", style.layers().len());
if let Some(name) = style.name() {
tracing::info!(" Style name: {}", name);
}
if let Some(zoom) = style.zoom() {
tracing::info!(" Default zoom: {}", zoom);
}
if let Some(center) = style.center() {
if center.len() >= 2 {
tracing::info!(" Default center: [{}, {}]", center[0], center[1]);
}
}
tracing::info!("");
tracing::info!(" Sample layers:");
for (i, layer) in style.layers().iter().take(5).enumerate() {
if let Some(id) = layer.get("id").and_then(|v| v.as_str()) {
let layer_type = layer
.get("type")
.and_then(|v| v.as_str())
.unwrap_or("unknown");
tracing::info!(" {}. {} (type: {})", i + 1, id, layer_type);
}
}
tracing::info!("");
tracing::info!("π‘ Style documents define:");
tracing::info!(" β’ Layer rendering order and visibility");
tracing::info!(" β’ Colors, widths, and symbols");
tracing::info!(" β’ Zoom level visibility ranges");
tracing::info!(" β’ Data source URLs");
Ok(())
}
async fn demonstrate_single_tile(vt_service: &VectorTileServiceClient<'_>) -> Result<()> {
tracing::info!("\n=== Example 2: Single Tile Fetch ===");
tracing::info!("Download one MVT tile in Protocol Buffer format");
tracing::info!("");
let tile = TileCoordinate::new(10, 396, 163);
tracing::info!(" Tile coordinates:");
tracing::info!(" Zoom level: {}", tile.level());
tracing::info!(" Row: {}", tile.row());
tracing::info!(" Column: {}", tile.col());
tracing::info!("");
let tile_data = vt_service.get_tile(&tile).await?;
assert!(!tile_data.is_empty(), "Tile data should not be empty");
tracing::info!("β
Retrieved vector tile");
tracing::info!(" Size: {} bytes", tile_data.len());
tracing::info!(" Format: MVT (Mapbox Vector Tile / Protocol Buffer)");
tracing::info!("");
tracing::info!("π‘ To decode MVT tiles:");
tracing::info!(" β’ Use mapbox-vector-tile crate");
tracing::info!(" β’ Extract layers and features");
tracing::info!(" β’ Render with MapLibre GL or similar");
Ok(())
}
async fn demonstrate_batch_tiles(vt_service: &VectorTileServiceClient<'_>) -> Result<()> {
tracing::info!("\n=== Example 3: Batch Tile Fetch ===");
tracing::info!("Download multiple tiles in a single operation");
tracing::info!("");
let tiles = vec![
TileCoordinate::new(10, 396, 163), TileCoordinate::new(10, 396, 164), TileCoordinate::new(10, 397, 163), TileCoordinate::new(10, 397, 164), ];
tracing::info!(" Fetching 2x2 tile grid:");
tracing::info!(" Zoom level: 10");
tracing::info!(" Rows: 396-397");
tracing::info!(" Columns: 163-164");
tracing::info!("");
let tile_data = vt_service.get_tiles(&tiles).await?;
assert_eq!(
tile_data.len(),
tiles.len(),
"Should retrieve all requested tiles, requested {}, got {}",
tiles.len(),
tile_data.len()
);
assert!(
tile_data.iter().all(|t| !t.is_empty()),
"All tiles should have data"
);
tracing::info!("β
Retrieved {} tiles", tile_data.len());
let total_size: usize = tile_data.iter().map(|t| t.len()).sum();
let avg_size = total_size / tile_data.len();
tracing::info!(" Total size: {} bytes", total_size);
tracing::info!(" Average tile size: {} bytes", avg_size);
tracing::info!("");
tracing::info!("π‘ Batch fetching:");
tracing::info!(" β’ More efficient than individual requests");
tracing::info!(" β’ Ideal for tile caching systems");
tracing::info!(" β’ Reduces HTTP overhead");
tracing::info!(" β’ Perfect for offline map generation");
Ok(())
}
async fn demonstrate_font_glyphs(vt_service: &VectorTileServiceClient<'_>) -> Result<()> {
tracing::info!("\n=== Example 4: Font Glyph Retrieval ===");
tracing::info!("Download font data for rendering text labels");
tracing::info!("");
let fonts = vec![
("Arial Unicode MS Regular", GlyphRange::ascii()),
("Noto Sans Regular", GlyphRange::basic_latin()),
];
let mut glyph_sizes = HashMap::new();
for (font_name, range) in &fonts {
tracing::debug!(font = %font_name, range = %range, "Fetching glyphs");
let font_stack = FontStack::new(*font_name);
let glyphs = vt_service.get_fonts(&font_stack, range).await?;
assert!(
!glyphs.is_empty(),
"Font glyphs should not be empty for {}",
font_name
);
glyph_sizes.insert(*font_name, glyphs.len());
tracing::info!("β
Retrieved glyphs: {}", font_name);
tracing::info!(" Range: {}", range);
tracing::info!(" Size: {} bytes", glyphs.len());
tracing::info!("");
}
tracing::info!("π‘ Font glyphs:");
tracing::info!(" β’ PBF format (Protocol Buffer)");
tracing::info!(" β’ Contains vector outlines for text rendering");
tracing::info!(" β’ Downloaded per Unicode range (0-255, 256-511, etc.)");
tracing::info!(" β’ Cached by web mapping libraries");
tracing::info!("");
tracing::info!(" Common ranges:");
tracing::info!(" β’ 0-255: ASCII and Latin-1");
tracing::info!(" β’ 256-511: Latin Extended");
tracing::info!(" β’ 1024-1279: Cyrillic");
tracing::info!(" β’ 19968-20479: CJK Unified Ideographs");
Ok(())
}
async fn demonstrate_sprite_resources(vt_service: &VectorTileServiceClient<'_>) -> Result<()> {
tracing::info!("\n=== Example 5: Sprite Sheet Resources ===");
tracing::info!("Download icon/symbol sprite metadata and images");
tracing::info!("");
tracing::debug!("Fetching sprite metadata");
let sprite_meta = vt_service.get_sprite_metadata().await?;
assert!(
sprite_meta.is_object(),
"Sprite metadata should be a JSON object"
);
let sprite_count = if sprite_meta.is_object() {
sprite_meta.as_object().map(|m| m.len()).unwrap_or(0)
} else {
0
};
tracing::info!("β
Retrieved sprite metadata");
tracing::info!(" Format: JSON");
tracing::info!(" Sprite count: {}", sprite_count);
if let Some(obj) = sprite_meta.as_object() {
tracing::info!(" Sample sprites:");
for (i, (name, _data)) in obj.iter().take(5).enumerate() {
tracing::info!(" {}. {}", i + 1, name);
}
}
tracing::info!("");
tracing::debug!("Fetching sprite image");
let sprite_image = vt_service.get_sprite_image().await?;
assert!(!sprite_image.is_empty(), "Sprite image should not be empty");
tracing::info!("β
Retrieved sprite image");
tracing::info!(" Format: PNG");
tracing::info!(" Size: {} bytes", sprite_image.len());
tracing::info!("");
tracing::info!("π‘ Sprite sheets:");
tracing::info!(" β’ PNG image containing all icons");
tracing::info!(" β’ JSON metadata describes each icon's position and size");
tracing::info!(" β’ Used for point symbols (markers, icons)");
tracing::info!(" β’ Web renderers extract individual icons from sheet");
tracing::info!("");
tracing::info!(" Common sprites:");
tracing::info!(" β’ Place markers (restaurant, hotel, etc.)");
tracing::info!(" β’ Transportation symbols (airport, parking)");
tracing::info!(" β’ Arrows and directional indicators");
tracing::info!(" β’ Custom brand icons");
Ok(())
}
fn print_best_practices() {
tracing::info!("\nπ‘ Vector Tile Best Practices:");
tracing::info!(" - Cache tiles locally for better performance");
tracing::info!(" - Use batch operations when downloading multiple tiles");
tracing::info!(" - Implement tile pyramid (multiple zoom levels)");
tracing::info!(" - Respect service rate limits and terms of use");
tracing::info!(" - Use HTTP/2 for efficient tile downloads");
tracing::info!("");
tracing::info!("π― Tile Caching Strategy:");
tracing::info!(" - File system: tiles/{{z}}/{{x}}/{{y}}.pbf");
tracing::info!(" - Database: SQLite with MBTiles format");
tracing::info!(" - Memory: LRU cache for frequently accessed tiles");
tracing::info!(" - CDN: CloudFront/Cloudflare for public maps");
tracing::info!("");
tracing::info!("β‘ Performance Tips:");
tracing::info!(" - Pre-download tiles for expected viewport");
tracing::info!(" - Use web workers for MVT decoding");
tracing::info!(" - Enable GZIP compression (often default)");
tracing::info!(" - Load higher zoom tiles progressively");
tracing::info!(" - Implement tile request coalescing");
tracing::info!("");
tracing::info!("π Web Integration:");
tracing::info!(" - MapLibre GL JS: Open-source Mapbox GL alternative");
tracing::info!(" - Leaflet: Via leaflet.vectorgrid plugin");
tracing::info!(" - OpenLayers: Built-in MVT support");
tracing::info!(" - Deck.gl: High-performance WebGL rendering");
tracing::info!("");
tracing::info!("π Coordinate Systems:");
tracing::info!(" - MVT uses local tile coordinates (0-4096)");
tracing::info!(" - Convert to/from geographic coordinates");
tracing::info!(" - Web Mercator (EPSG:3857) is standard");
tracing::info!(" - Tile coordinates follow XYZ scheme (zoom, x, y)");
}