Asset Management - Black-Lights/planetscope-py GitHub Wiki
Asset Management
Intelligent quota monitoring, asset activation, and download management
The Asset Management module provides comprehensive capabilities for managing Planet subscription quotas, activating assets, and downloading PlanetScope imagery with intelligent progress tracking and user confirmation workflows.
Overview
The AssetManager
handles the complete asset lifecycle including:
- Quota Monitoring: Real-time tracking of subscription usage across multiple Planet APIs
- Asset Activation: Automated asset processing with status tracking
- Download Management: Parallel downloads with retry logic and progress tracking
- User Confirmation: Interactive workflows with impact assessment
- ROI Clipping: Automatic scene clipping to regions of interest
Quick Start
Basic Asset Management
from planetscope_py import AssetManager, PlanetScopeQuery
# Initialize with authentication
query = PlanetScopeQuery()
asset_manager = AssetManager(query.auth)
# Check current quota usage
quota_info = await asset_manager.get_quota_info()
print(f"Current usage: {quota_info.used_area_km2:.1f} / {quota_info.limit_area_km2} km²")
print(f"Remaining: {quota_info.remaining_area_km2:.1f} km²")
print(f"Usage: {quota_info.usage_percentage:.1%}")
# Download assets with confirmation
selected_scenes = results['features'][:10] # First 10 scenes
downloads = await asset_manager.activate_and_download_assets(
scenes=selected_scenes,
asset_types=["ortho_analytic_4b"],
output_dir="downloads",
clip_to_roi=roi
)
print(f"Downloaded {len(downloads)} assets")
Quota Management
Checking Quota Usage
# Get comprehensive quota information
quota_info = await asset_manager.get_quota_info()
print("Quota Information:")
print(f" Monthly Limit: {quota_info.monthly_limit_km2:,.0f} km²")
print(f" Current Usage: {quota_info.current_usage_km2:,.0f} km²")
print(f" Remaining: {quota_info.remaining_area_km2:,.0f} km²")
print(f" Usage Percentage: {quota_info.usage_percentage:.1%}")
# Check if near limit
if quota_info.is_near_limit:
print("⚠️ WARNING: Usage is near monthly limit")
# Check if download is possible
download_estimate = asset_manager.calculate_download_impact(selected_scenes, roi)
if quota_info.can_download(download_estimate['area_km2']):
print("✅ Download is within quota limits")
else:
print("❌ Download would exceed quota limits")
Download Impact Calculation
# Calculate impact before downloading
scenes_to_download = results['features'][:20]
roi = box(9.04, 45.40, 9.28, 45.52)
impact = asset_manager.calculate_download_impact(scenes_to_download, roi)
print("Download Impact Analysis:")
print(f" Number of scenes: {impact['scene_count']}")
print(f" Total area: {impact['area_km2']:.2f} km²")
print(f" Average scene size: {impact['avg_scene_area_km2']:.2f} km²")
print(f" Estimated file size: {impact['estimated_file_size_gb']:.1f} GB")
# Check quota impact
print(f" Current usage: {quota_info.current_usage_km2:.1f} km²")
print(f" After download: {quota_info.current_usage_km2 + impact['area_km2']:.1f} km²")
print(f" Remaining after: {quota_info.remaining_area_km2 - impact['area_km2']:.1f} km²")
Asset Activation and Download
Basic Download Workflow
# Complete download workflow with user confirmation
downloads = await asset_manager.activate_and_download_assets(
scenes=selected_scenes,
asset_types=["ortho_analytic_4b", "ortho_visual"], # Multiple asset types
output_dir="planet_downloads",
clip_to_roi=roi,
confirm_download=True, # Ask for user confirmation
max_concurrent=3 # Limit concurrent downloads
)
# Check download results
successful_downloads = [d for d in downloads if d.status == AssetStatus.COMPLETED]
failed_downloads = [d for d in downloads if d.status == AssetStatus.FAILED]
print(f"Successful: {len(successful_downloads)}")
print(f"Failed: {len(failed_downloads)}")
Advanced Download Configuration
# Configure download behavior
config = {
'max_concurrent_downloads': 5,
'download_chunk_size': 16384, # 16KB chunks
'retry_attempts': 3,
'retry_delay': 5.0, # seconds
'timeout': 300, # 5 minutes per download
'rate_limits': {
'activation': 5, # 5 activations per second
'download': 10 # 10 downloads per second
}
}
asset_manager = AssetManager(query.auth, config)
# Download with custom configuration
downloads = await asset_manager.activate_and_download_assets(
scenes=scenes,
asset_types=["ortho_analytic_4b"],
output_dir="downloads",
max_concurrent=config['max_concurrent_downloads']
)
Progress Tracking
# Set up progress callback
def download_progress_callback(job: DownloadJob):
"""Custom progress tracking callback."""
if job.progress_percentage > 0:
print(f"{job.scene_id}: {job.progress_percentage:.1f}% - {job.status.value}")
asset_manager.progress_callback = download_progress_callback
# Download with progress tracking
downloads = await asset_manager.activate_and_download_assets(
scenes=scenes,
asset_types=["ortho_analytic_4b"]
)
User Confirmation Workflows
Interactive Confirmation
# The system automatically shows confirmation dialog:
"""
==================================================
DOWNLOAD CONFIRMATION
==================================================
Scenes to download: 15
Asset types: ['ortho_analytic_4b']
Total estimated area: 245.67 km²
Estimated file size: 3.2 GB
Current Quota Status:
Used: 1,234 / 3,000 km² (41.1%)
Remaining: 1,766 km²
Required: 245.67 km²
Available after download: 1,520.33 km²
==================================================
Proceed with download? (y/n):
"""
# Automatic approval for scripted workflows
downloads = await asset_manager.activate_and_download_assets(
scenes=scenes,
confirm_download=False # Skip confirmation
)
Custom Confirmation Logic
# Implement custom confirmation logic
def custom_confirmation_logic(quota_info, download_impact):
"""Custom logic for download approval."""
# Always approve small downloads
if download_impact['area_km2'] < 50:
return True
# Check business hours
from datetime import datetime
if datetime.now().hour < 9 or datetime.now().hour > 17:
print("Large downloads only during business hours")
return False
# Check remaining quota percentage
remaining_after = quota_info.remaining_area_km2 - download_impact['area_km2']
remaining_percentage = remaining_after / quota_info.monthly_limit_km2
if remaining_percentage < 0.2: # Keep 20% buffer
print("Download would leave less than 20% quota remaining")
return False
return True
# Override confirmation method
asset_manager.get_user_confirmation = custom_confirmation_logic
Asset Types and Formats
Available Asset Types
# Common PlanetScope asset types
asset_types = [
"ortho_analytic_4b", # 4-band analytic (Blue, Green, Red, NIR)
"ortho_analytic_8b", # 8-band analytic (Coastal, Blue, Green I, Green, Yellow, Red, Red Edge, NIR)
"ortho_visual", # 3-band visual (RGB)
"ortho_analytic_sr", # Surface reflectance
"ortho_udm2" # Usable data mask
]
# Download multiple asset types
downloads = await asset_manager.activate_and_download_assets(
scenes=scenes,
asset_types=["ortho_analytic_4b", "ortho_visual", "ortho_udm2"]
)
# Organize downloads by asset type
by_asset_type = {}
for download in downloads:
asset_type = download.asset_type
if asset_type not in by_asset_type:
by_asset_type[asset_type] = []
by_asset_type[asset_type].append(download)
for asset_type, jobs in by_asset_type.items():
successful = [j for j in jobs if j.status == AssetStatus.COMPLETED]
print(f"{asset_type}: {len(successful)}/{len(jobs)} successful")
ROI Clipping
Automatic Clipping During Download
from shapely.geometry import box
# Define region of interest
roi = box(9.04, 45.40, 9.28, 45.52) # Milan center
# Download with automatic ROI clipping
downloads = await asset_manager.activate_and_download_assets(
scenes=scenes,
asset_types=["ortho_analytic_4b"],
clip_to_roi=roi, # Enable ROI clipping
output_dir="clipped_downloads"
)
# Clipped files are automatically saved with "_clipped" suffix
for download in downloads:
if download.status == AssetStatus.COMPLETED:
print(f"Original: {download.original_file_path}")
print(f"Clipped: {download.clipped_file_path}")
Custom Clipping Configuration
# Configure clipping behavior
clipping_config = {
'clip_to_roi': True,
'buffer_meters': 100, # Add 100m buffer around ROI
'output_format': 'GTiff',
'compression': 'LZW',
'create_overview': True
}
# Apply custom clipping configuration
downloads = await asset_manager.activate_and_download_assets(
scenes=scenes,
clip_to_roi=roi,
clipping_config=clipping_config
)
Download Job Management
DownloadJob Status Tracking
from planetscope_py import AssetStatus
# Monitor download job status
for job in downloads:
print(f"Scene: {job.scene_id}")
print(f"Asset: {job.asset_type}")
print(f"Status: {job.status.value}")
print(f"Progress: {job.progress_percentage:.1f}%")
if job.status == AssetStatus.COMPLETED:
print(f"File: {job.downloaded_file_path}")
print(f"Size: {job.file_size_mb:.1f} MB")
elif job.status == AssetStatus.FAILED:
print(f"Error: {job.error_message}")
print("---")
Retry Failed Downloads
# Identify failed downloads
failed_jobs = [job for job in downloads if job.status == AssetStatus.FAILED]
if failed_jobs:
print(f"Retrying {len(failed_jobs)} failed downloads...")
# Retry failed downloads
retry_downloads = await asset_manager.retry_failed_downloads(failed_jobs)
# Check retry results
retry_successful = [d for d in retry_downloads if d.status == AssetStatus.COMPLETED]
print(f"Retry successful: {len(retry_successful)}/{len(failed_jobs)}")
Error Handling
Common Download Errors
from planetscope_py.exceptions import AssetError
try:
downloads = await asset_manager.activate_and_download_assets(scenes=scenes)
except AssetError as e:
print(f"Asset management error: {e}")
# Handle specific error types
if "quota exceeded" in str(e).lower():
print("Quota limit reached - try fewer scenes")
elif "activation failed" in str(e).lower():
print("Asset activation failed - check scene availability")
elif "download failed" in str(e).lower():
print("Download failed - check network connection")
except Exception as e:
print(f"Unexpected error: {e}")
Robust Download with Error Recovery
async def robust_download_workflow(asset_manager, scenes, roi):
"""Robust download workflow with comprehensive error handling."""
try:
# Check quota first
quota_info = await asset_manager.get_quota_info()
download_impact = asset_manager.calculate_download_impact(scenes, roi)
if not quota_info.can_download(download_impact['area_km2']):
print("Insufficient quota - reducing scene count")
max_scenes = int(quota_info.remaining_area_km2 / download_impact['avg_scene_area_km2'])
scenes = scenes[:max_scenes]
# Start download
downloads = await asset_manager.activate_and_download_assets(
scenes=scenes,
clip_to_roi=roi,
confirm_download=False
)
# Handle partial failures
successful = [d for d in downloads if d.status == AssetStatus.COMPLETED]
failed = [d for d in downloads if d.status == AssetStatus.FAILED]
if failed:
print(f"Initial download: {len(successful)}/{len(downloads)} successful")
print("Retrying failed downloads...")
retry_downloads = await asset_manager.retry_failed_downloads(failed)
retry_successful = [d for d in retry_downloads if d.status == AssetStatus.COMPLETED]
total_successful = len(successful) + len(retry_successful)
print(f"Final result: {total_successful}/{len(downloads)} successful")
return successful + retry_successful
except AssetError as e:
print(f"Asset management error: {e}")
return []
Performance Optimization
Concurrent Download Management
# Optimize for different scenarios
# For fast connections - increase concurrency
fast_config = {
'max_concurrent_downloads': 10,
'download_chunk_size': 32768 # 32KB chunks
}
# For slow connections - reduce concurrency
slow_config = {
'max_concurrent_downloads': 2,
'download_chunk_size': 8192 # 8KB chunks
}
# For large files - increase chunk size
large_file_config = {
'max_concurrent_downloads': 5,
'download_chunk_size': 65536 # 64KB chunks
}
Memory Management
# For memory-constrained environments
memory_efficient_config = {
'max_concurrent_downloads': 2, # Reduce concurrency
'download_chunk_size': 4096, # Smaller chunks
'cleanup_temp_files': True # Auto-cleanup
}
asset_manager = AssetManager(auth, memory_efficient_config)
# Process in batches for large scene lists
def batch_download(scenes, batch_size=50):
"""Download scenes in batches to manage memory."""
results = []
for i in range(0, len(scenes), batch_size):
batch = scenes[i:i + batch_size]
print(f"Processing batch {i//batch_size + 1}: {len(batch)} scenes")
batch_downloads = await asset_manager.activate_and_download_assets(
scenes=batch,
confirm_download=False
)
results.extend(batch_downloads)
return results
Integration with Other Components
With Scene Discovery
from planetscope_py import PlanetScopeQuery
# Integrated workflow: search and download
query = PlanetScopeQuery()
asset_manager = AssetManager(query.auth)
# Search for scenes
results = query.search_scenes(
geometry=roi,
start_date="2024-01-01",
end_date="2024-06-30",
cloud_cover_max=0.2
)
# Quality filter before download
high_quality_scenes = []
for scene in results['features']:
props = scene['properties']
if props.get('sun_elevation', 0) > 30: # Good sun angle
high_quality_scenes.append(scene)
# Download high-quality scenes
downloads = await asset_manager.activate_and_download_assets(
scenes=high_quality_scenes,
clip_to_roi=roi
)
With GeoPackage Export
from planetscope_py import GeoPackageManager
# Download and immediately export to GeoPackage
downloads = await asset_manager.activate_and_download_assets(scenes=scenes)
# Get downloaded file paths
downloaded_files = [
d.downloaded_file_path for d in downloads
if d.status == AssetStatus.COMPLETED
]
# Create GeoPackage with imagery
geopackage_manager = GeoPackageManager()
geopackage_manager.create_scene_geopackage(
scenes=scenes,
output_path="analysis_with_imagery.gpkg",
roi=roi,
downloaded_files=downloaded_files
)
Best Practices
1. Monitor Quota Usage
# Regular quota monitoring
async def monitor_quota_usage():
quota_info = await asset_manager.get_quota_info()
print(f"Quota Status: {quota_info.usage_percentage:.1%}")
if quota_info.is_near_limit:
# Send alert or take action
print("⚠️ Near quota limit - consider upgrading subscription")
return quota_info
# Check quota before major operations
quota_info = await monitor_quota_usage()
2. Efficient Scene Selection
# Pre-filter scenes to optimize quota usage
def optimize_scene_selection(scenes, roi, max_area_km2):
"""Select scenes efficiently to stay within quota."""
from planetscope_py.utils import calculate_area_km2
# Calculate area for each scene
scene_areas = []
for scene in scenes:
geom = shape(scene['geometry'])
area = calculate_area_km2(geom)
scene_areas.append((scene, area))
# Sort by area (prefer smaller scenes for better coverage)
scene_areas.sort(key=lambda x: x[1])
# Select scenes up to quota limit
selected_scenes = []
total_area = 0
for scene, area in scene_areas:
if total_area + area <= max_area_km2:
selected_scenes.append(scene)
total_area += area
else:
break
return selected_scenes
# Apply optimization
optimized_scenes = optimize_scene_selection(
scenes=results['features'],
roi=roi,
max_area_km2=quota_info.remaining_area_km2 * 0.8 # Use 80% of remaining
)
3. Automated Download Management
# Set up automated download management
class AutomatedDownloadManager:
def __init__(self, asset_manager):
self.asset_manager = asset_manager
self.download_queue = []
self.max_daily_quota = 500 # km²
async def add_to_queue(self, scenes, priority="normal"):
"""Add scenes to download queue with priority."""
self.download_queue.append({
'scenes': scenes,
'priority': priority,
'added_at': datetime.now()
})
async def process_queue(self):
"""Process download queue based on quota availability."""
quota_info = await self.asset_manager.get_quota_info()
if quota_info.remaining_area_km2 < 100: # Less than 100 km² remaining
print("Quota too low - skipping downloads")
return
# Sort queue by priority
self.download_queue.sort(
key=lambda x: (x['priority'] == 'high', -x['added_at'].timestamp())
)
for item in self.download_queue[:]:
impact = self.asset_manager.calculate_download_impact(item['scenes'])
if impact['area_km2'] <= quota_info.remaining_area_km2:
print(f"Processing {len(item['scenes'])} scenes...")
downloads = await self.asset_manager.activate_and_download_assets(
scenes=item['scenes'],
confirm_download=False
)
self.download_queue.remove(item)
# Update quota info
quota_info = await self.asset_manager.get_quota_info()
else:
print("Insufficient quota for remaining items")
break
# Usage
auto_manager = AutomatedDownloadManager(asset_manager)
await auto_manager.add_to_queue(high_priority_scenes, priority="high")
await auto_manager.add_to_queue(regular_scenes, priority="normal")
await auto_manager.process_queue()
Troubleshooting
Common Issues
1. Quota exceeded errors
# Check quota before operations
quota_info = await asset_manager.get_quota_info()
if quota_info.usage_percentage > 0.9:
print("Quota usage > 90% - consider reducing download scope")
2. Download timeouts
# Increase timeout for slow connections
config = {
'timeout': 600, # 10 minutes
'retry_attempts': 5,
'retry_delay': 10.0
}
asset_manager = AssetManager(auth, config)
3. Asset activation failures
# Check asset availability before download
async def check_asset_availability(scene_id, asset_type):
"""Check if asset is available for download."""
try:
asset_info = await asset_manager.get_asset_info(scene_id, asset_type)
return asset_info['status'] in ['active', 'activating']
except:
return False
# Filter available assets
available_scenes = []
for scene in scenes:
if await check_asset_availability(scene['properties']['id'], 'ortho_analytic_4b'):
available_scenes.append(scene)
4. Network connection issues
# Implement connection retry logic
async def robust_asset_manager_operation(operation_func, *args, **kwargs):
"""Robust wrapper for asset manager operations."""
max_retries = 3
for attempt in range(max_retries):
try:
return await operation_func(*args, **kwargs)
except ConnectionError as e:
if attempt < max_retries - 1:
wait_time = 2 ** attempt # Exponential backoff
print(f"Connection error, retrying in {wait_time}s...")
await asyncio.sleep(wait_time)
else:
raise e
# Usage
downloads = await robust_asset_manager_operation(
asset_manager.activate_and_download_assets,
scenes=scenes
)
Examples
See the Asset Management Examples page for detailed workflow examples and use cases.