open-geodata-api


Nameopen-geodata-api JSON
Version 0.3.0 PyPI version JSON
download
home_pageNone
SummaryUnified Python client for open geospatial data APIs: Planetary Computer, EarthSearch, and more
upload_time2025-07-13 17:58:11
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseNone
keywords api-client cli earth-observation earthsearch geodata geospatial open-data planetary-computer remote-sensing satellite stac
VCS
bugtrack_url
requirements requests pandas sphinx-copybutton
Travis-CI No Travis.
coveralls test coverage No coveralls.
            <p align="center">
  <img src="https://github.com/Mirjan-Ali-Sha/open-geodata-api/blob/main/icon.png" alt="Open Geodata API Icon" width="150" height="150" />
</p>

# Open Geodata API - Complete User Guide

## Table of Contents

1. [Introduction](README.md#introduction)
2. [Open Geodata API - Complete Tool Summary](README.md#open-geodata-api---complete-tool-summary)
3. [Installation](README.md#installation)
4. [Quick Start](README.md#quick-start)
5. [Core Concepts](README.md#core-concepts)
6. [API Reference](README.md#api-reference)
7. [Usage Examples](README.md#usage-examples)
8. [Best Practices](README.md#best-practices)
9. [Troubleshooting](README.md#troubleshooting)
10. [Advanced Usage](README.md#advanced-usage)
11. [Utility Functions](README.md#utils-functions)
12. [CLI Usage](README.md#cli-usage)
13. [FAQ](README.md#faq)

## Introduction

### What is Open Geodata API?

**Open Geodata API** is a unified Python client library that provides seamless access to multiple open geospatial data APIs. It focuses on **API access, search, and URL management** while maintaining maximum flexibility for data reading and processing.

## Open Geodata API - Complete Tool Summary

**šŸ›°ļø Unified Python Client for Satellite Data Access**

#### **What It Does**

āœ… **One Interface** - Access Microsoft Planetary Computer \& AWS EarthSearch APIs<br>
āœ… **Smart URLs** - Automatic signing, validation, and expiration handling<br>
āœ… **Your Choice** - Use any raster package (rioxarray, rasterio, GDAL)<br>
āœ… **Complete Workflow** - Search → Filter → Download → Analyze

#### **Key Capabilities**

āœ… **Python API** - Programmatic access with full flexibility<br>
āœ… **Command Line** - `ogapi` CLI for all operations with help<br>
āœ… **Smart Downloads** - Progress tracking, resume, batch processing<br>
āœ… **Data Filtering** - Cloud cover, temporal, quality-based filtering<br>
āœ… **URL Management** - Export, validate, and refresh URLs automatically


### Supported APIs

| API | Provider | Authentication | URL Handling |
| :-- | :-- | :-- | :-- |
| **Planetary Computer** | Microsoft | API Key + Signing | Automatic signing |
| **EarthSearch** | Element84/AWS | None required | URL validation |

### Philosophy

šŸŽÆ **Core Focus**: We provide URLs - you choose how to read them!<br>
šŸ“¦ **Use Any Package**: rioxarray, rasterio, GDAL, or any package you prefer<br>
šŸš€ **Maximum Flexibility**: Zero restrictions on your workflow

## Installation

### Basic Installation

```bash
# Install core package
pip install open-geodata-api
```


### Optional Dependencies

```bash
# For spatial analysis (shapely, geopandas)
pip install open-geodata-api[spatial]

# For raster reading suggestions (rioxarray,rasterio, xarray)
pip install open-geodata-api[io]  # rioxarray + xarray

# For complete examples (shapely, geopandas, rioxarray, rasterio, xarray)
pip install open-geodata-api[complete]

# Development dependencies
pip install open-geodata-api[dev]
```


### Verify Installation

```python
import open_geodata_api as ogapi
ogapi.info()
```


## Quick Start

### 30-Second Example

```python
import open_geodata_api as ogapi

# Get clients for both APIs
clients = ogapi.get_clients(pc_auto_sign=True)
pc = clients['planetary_computer']
es = clients['earth_search']

# Search for Sentinel-2 data
results = pc.search(
    collections=["sentinel-2-l2a"],
    bbox=[-122.5, 47.5, -122.0, 48.0],
    datetime="2024-01-01/2024-03-31"
)

# Get items and URLs
items = results.get_all_items()
item = items[0]

# Get ready-to-use URLs
blue_url = item.get_asset_url('B02')  # Automatically signed!
all_urls = item.get_all_asset_urls()  # All assets

# Use with ANY raster package
import rioxarray
data = rioxarray.open_rasterio(blue_url)

# Or use with rasterio
import rasterio
with rasterio.open(blue_url) as src:
    data = src.read(1)
```


### 5-Minute Tutorial

```python
# 1. Import and setup
import open_geodata_api as ogapi

# 2. Create clients
pc = ogapi.planetary_computer(auto_sign=True)
es = ogapi.earth_search()

# 3. Search for data
search_params = {
    'collections': ['sentinel-2-l2a'],
    'bbox': [-122.5, 47.5, -122.0, 48.0],
    'datetime': '2024-01-01/2024-03-31',
    'query': {'eo:cloud_cover': {'lt': 30}}
}

pc_results = pc.search(**search_params, limit=10)
es_results = es.search(**search_params, limit=10)

# 4. Work with results
pc_items = pc_results.get_all_items()
es_items = es_results.get_all_items()

print(f"Found: PC={len(pc_items)}, ES={len(es_items)} items")

# 5. Get URLs and use with your preferred package
item = pc_items[0]
item.print_assets_info()

# Get specific bands
rgb_urls = item.get_band_urls(['B04', 'B03', 'B02'])  # Red, Green, Blue
print(f"RGB URLs: {rgb_urls}")

# Use URLs with any package you want!
```


## Core Concepts

### STAC (SpatioTemporal Asset Catalog)

Open Geodata API works with STAC-compliant APIs. Key STAC concepts:

- **Collections**: Groups of related datasets (e.g., "sentinel-2-l2a")
- **Items**: Individual products/scenes with metadata
- **Assets**: Individual files (bands, thumbnails, metadata)


### Package Architecture

```
open-geodata-api/
ā”œā”€ā”€ Core Classes (Universal)
│   ā”œā”€ā”€ STACItem           # Individual products
│   ā”œā”€ā”€ STACItemCollection # Groups of products  
│   ā”œā”€ā”€ STACAsset          # Individual files
│   └── STACSearch         # Search results
ā”œā”€ā”€ API Clients
│   ā”œā”€ā”€ PlanetaryComputerCollections
│   └── EarthSearchCollections
└── Utilities
    ā”œā”€ā”€ URL signing (PC)
    ā”œā”€ā”€ URL validation (ES)
    └── Filtering functions
```


### Provider-Specific Handling

| Feature | Planetary Computer | EarthSearch |
| :-- | :-- | :-- |
| **Authentication** | Automatic via planetary-computer package | None required |
| **URL Signing** | Automatic (auto_sign=True) | Not applicable |
| **Asset Naming** | B01, B02, B03... | coastal, blue, green... |
| **Cloud Cover** | eo:cloud_cover | eo:cloud_cover |

## API Reference

### Factory Functions

#### `planetary_computer(auto_sign=False)`

Creates a Planetary Computer client.

**Parameters:**

- `auto_sign` (bool): Automatically sign URLs for immediate use

**Returns:** `PlanetaryComputerCollections` instance

#### `earth_search(auto_validate=False)`

Creates an EarthSearch client.

**Parameters:**

- `auto_validate` (bool): Validate URLs (currently placeholder)

**Returns:** `EarthSearchCollections` instance

#### `get_clients(pc_auto_sign=False, es_auto_validate=False)`

Creates both clients simultaneously.

**Returns:** Dictionary with 'planetary_computer' and 'earth_search' keys

### Client Methods

#### `search(collections, bbox=None, datetime=None, query=None, limit=100)`

Search for STAC items.

**Parameters:**

- `collections` (list): Collection IDs to search
- `bbox` (list): Bounding box [west, south, east, north]
- `datetime` (str): Date range "YYYY-MM-DD/YYYY-MM-DD"
- `query` (dict): Additional filters like `{"eo:cloud_cover": {"lt": 30}}`
- `limit` (int): Maximum results to return

**Returns:** `STACSearch` instance

#### `list_collections()`

Get list of available collection names.

**Returns:** List of collection ID strings

#### `get_collection_info(collection_name)`

Get detailed information about a specific collection.

**Returns:** Collection metadata dictionary

### STACItem Methods

#### `get_asset_url(asset_key, signed=None)`

Get ready-to-use URL for a specific asset.

**Parameters:**

- `asset_key` (str): Asset name (e.g., 'B02', 'blue', 'red')
- `signed` (bool): Override automatic signing behavior

**Returns:** URL string ready for any raster package

#### `get_all_asset_urls(signed=None)`

Get URLs for all available assets.

**Returns:** Dictionary `{asset_key: url}`

#### `get_band_urls(bands, signed=None)`

Get URLs for specific bands/assets.

**Parameters:**

- `bands` (list): List of asset names

**Returns:** Dictionary `{asset_key: url}`

#### `list_assets()`

Get list of available asset names.

**Returns:** List of asset key strings

#### `print_assets_info()`

Print detailed information about all assets.

### STACItemCollection Methods

#### `get_all_urls(asset_keys=None, signed=None)`

Get URLs from all items in the collection.

**Parameters:**

- `asset_keys` (list, optional): Specific assets to get URLs for
- `signed` (bool, optional): Override signing behavior

**Returns:** Dictionary `{item_id: {asset_key: url}}`

#### `to_dataframe(include_geometry=True)`

Convert collection to pandas/geopandas DataFrame.

**Parameters:**

- `include_geometry` (bool): Include spatial geometry (requires geopandas)

**Returns:** DataFrame with item metadata

#### `export_urls_json(filename, asset_keys=None)`

Export all URLs to JSON file for external processing.

## Usage Examples

### Example 1: Simple Data Discovery

```python
import open_geodata_api as ogapi

# Setup
pc = ogapi.planetary_computer(auto_sign=True)

# Find available collections
collections = pc.list_collections()
sentinel_collections = [c for c in collections if 'sentinel' in c.lower()]
print(f"Sentinel collections: {sentinel_collections}")

# Get collection details
s2_info = pc.get_collection_info('sentinel-2-l2a')
print(f"Sentinel-2 L2A: {s2_info['title']}")
print(f"Description: {s2_info['description'][:100]}...")
```


### Example 2: Geographic Search

```python
# Search around San Francisco Bay Area
bbox = [-122.5, 37.5, -122.0, 38.0]

results = pc.search(
    collections=['sentinel-2-l2a'],
    bbox=bbox,
    datetime='2024-06-01/2024-08-31',
    query={'eo:cloud_cover': {'lt': 20}},  # Less than 20% clouds
    limit=20
)

items = results.get_all_items()
print(f"Found {len(items)} items with <20% cloud cover")

# Convert to DataFrame for analysis
df = items.to_dataframe()
print(f"Date range: {df['datetime'].min()} to {df['datetime'].max()}")
print(f"Cloud cover range: {df['eo:cloud_cover'].min():.1f}% to {df['eo:cloud_cover'].max():.1f}%")
```


### Example 3: Multi-Provider Comparison

```python
# Compare results from both providers
bbox = [-122.2, 47.6, -122.1, 47.7]  # Seattle area

pc_results = pc.search(
    collections=['sentinel-2-l2a'],
    bbox=bbox,
    datetime='2024-01-01/2024-03-31'
)

es_results = es.search(
    collections=['sentinel-2-l2a'], 
    bbox=bbox,
    datetime='2024-01-01T00:00:00Z/2024-03-31T23:59:59Z'
)

pc_items = pc_results.get_all_items()
es_items = es_results.get_all_items()

print(f"Planetary Computer: {len(pc_items)} items")
print(f"EarthSearch: {len(es_items)} items")

# Compare asset availability
if pc_items and es_items:
    pc_assets = pc_items[0].list_assets()
    es_assets = es_items[0].list_assets()
    
    print(f"PC assets: {pc_assets[:5]}")
    print(f"ES assets: {es_assets[:5]}")
```


### Example 4: URL Export for External Processing

```python
# Get URLs for specific bands across multiple items
items = pc_results.get_all_items()

# Export RGB band URLs
rgb_urls = items.get_all_urls(['B04', 'B03', 'B02'])  # Red, Green, Blue

# Save to JSON for external processing
items.export_urls_json('sentinel2_rgb_urls.json', ['B04', 'B03', 'B02'])

# Use the URLs with any package
first_item_urls = rgb_urls[list(rgb_urls.keys())[0]]
print(f"Red band URL: {first_item_urls['B04']}")

# Example with different raster packages
import rioxarray
import rasterio
from osgeo import gdal

red_url = first_item_urls['B04']

# Option 1: rioxarray
red_data_xr = rioxarray.open_rasterio(red_url)

# Option 2: rasterio
with rasterio.open(red_url) as src:
    red_data_rio = src.read(1)

# Option 3: GDAL
red_ds = gdal.Open(red_url)
red_data_gdal = red_ds.ReadAsArray()

print(f"Data shapes - XR: {red_data_xr.shape}, RIO: {red_data_rio.shape}, GDAL: {red_data_gdal.shape}")
```


### Example 5: Batch Processing Setup

```python
# Setup for batch processing
import json

# Search for monthly data
results = pc.search(
    collections=['sentinel-2-l2a'],
    bbox=[-120.0, 35.0, -119.0, 36.0],
    datetime='2024-01-01/2024-12-31',
    query={'eo:cloud_cover': {'lt': 15}},
    limit=100
)

items = results.get_all_items()
print(f"Found {len(items)} low-cloud scenes")

# Group by month
df = items.to_dataframe()
df['month'] = df['datetime'].str[:7]  # YYYY-MM
monthly_counts = df.groupby('month').size()
print("Monthly data availability:")
print(monthly_counts)

# Export all URLs for batch processing
all_urls = items.get_all_urls(['B04', 'B03', 'B02', 'B08'])  # RGB + NIR

# Save configuration for external processing
config = {
    'search_params': {
        'bbox': [-120.0, 35.0, -119.0, 36.0],
        'datetime': '2024-01-01/2024-12-31',
        'collections': ['sentinel-2-l2a']
    },
    'items_found': len(items),
    'urls': all_urls
}

with open('batch_processing_config.json', 'w') as f:
    json.dump(config, f, indent=2)

print("Batch processing configuration saved!")
```


### Example 6: EarthSearch Specific Features

```python
# EarthSearch uses different asset names
es = ogapi.earth_search()

es_results = es.search(
    collections=['sentinel-2-l2a'],
    bbox=[-122.5, 47.5, -122.0, 48.0],
    datetime='2024-06-01T00:00:00Z/2024-08-31T23:59:59Z',
    limit=5
)

es_items = es_results.get_all_items()
item = es_items[0]

# EarthSearch asset names
item.print_assets_info()

# Get URLs using EarthSearch naming
rgb_urls = item.get_band_urls(['red', 'green', 'blue'])
nir_url = item.get_asset_url('nir')

print(f"RGB URLs: {list(rgb_urls.keys())}")
print(f"NIR URL ready: {nir_url[:50]}...")

# All URLs (no signing needed for EarthSearch)
all_urls = item.get_all_asset_urls()
print(f"Total assets available: {len(all_urls)}")
```


## Best Practices

### 1. Client Configuration

```python
# Recommended setup
import open_geodata_api as ogapi

# Auto-sign PC URLs for immediate use
pc = ogapi.planetary_computer(auto_sign=True)
es = ogapi.earth_search()

# Or get both at once
clients = ogapi.get_clients(pc_auto_sign=True)
```


### 2. Search Strategy

```python
# Start with broad search, then refine
results = pc.search(
    collections=['sentinel-2-l2a'],
    bbox=your_bbox,
    datetime='2024-01-01/2024-12-31',
    query={'eo:cloud_cover': {'lt': 50}},  # Start broad
    limit=100
)

# Filter further based on your needs
df = results.get_all_items().to_dataframe()
filtered_df = df[df['eo:cloud_cover'] < 20]  # Refine cloud cover
```


### 3. URL Management

```python
# Let the package handle URL signing automatically
item = items[0]

# This automatically handles signing based on provider
blue_url = item.get_asset_url('B02')  # PC: signed, ES: validated

# Override if needed
unsigned_url = item.get_asset_url('B02', signed=False)
```


### 4. Asset Name Handling

```python
# Handle different naming conventions gracefully
def get_rgb_urls(item):
    """Get RGB URLs regardless of provider naming."""
    assets = item.list_assets()
    
    # Try Planetary Computer naming
    if all(band in assets for band in ['B04', 'B03', 'B02']):
        return item.get_band_urls(['B04', 'B03', 'B02'])
    
    # Try EarthSearch naming  
    elif all(band in assets for band in ['red', 'green', 'blue']):
        return item.get_band_urls(['red', 'green', 'blue'])
    
    else:
        print(f"Available assets: {assets}")
        return {}

# Use the function
rgb_urls = get_rgb_urls(item)
```


### 5. Error Handling

```python
# Robust search with error handling
def safe_search(client, **kwargs):
    """Search with comprehensive error handling."""
    try:
        results = client.search(**kwargs)
        items = results.get_all_items()
        
        if len(items) == 0:
            print("No items found. Try adjusting search parameters.")
            return None
            
        print(f"Found {len(items)} items")
        return items
        
    except Exception as e:
        print(f"Search failed: {e}")
        return None

# Use robust search
items = safe_search(
    pc,
    collections=['sentinel-2-l2a'],
    bbox=your_bbox,
    datetime='2024-01-01/2024-03-31'
)
```


### 6. Memory Management

```python
# For large datasets, process in batches
def process_in_batches(items, batch_size=10):
    """Process items in batches to manage memory."""
    for i in range(0, len(items), batch_size):
        batch = items[i:i+batch_size]
        
        # Get URLs for this batch
        batch_urls = {}
        for item in batch:
            try:
                batch_urls[item.id] = item.get_band_urls(['B04', 'B03', 'B02'])
            except Exception as e:
                print(f"Failed to get URLs for {item.id}: {e}")
        
        # Process batch_urls as needed
        yield batch_urls

# Use batch processing
for batch_urls in process_in_batches(items):
    print(f"Processing batch with {len(batch_urls)} items")
    # Your processing logic here
```


## Troubleshooting

### Common Issues and Solutions

#### Issue: "planetary-computer package not found"

**Problem:** PC URL signing fails

```python
# Error: planetary-computer package not found, returning unsigned URL
```

**Solution:**

```bash
pip install planetary-computer
```


#### Issue: No items found

**Problem:** Search returns empty results

**Solutions:**

```python
# 1. Check collection names
available_collections = pc.list_collections()
print("Available collections:", available_collections)

# 2. Expand search area
bbox = [-123.0, 47.0, -121.0, 48.0]  # Larger area

# 3. Expand date range
datetime = '2023-01-01/2024-12-31'  # Larger time window

# 4. Relax cloud cover
query = {'eo:cloud_cover': {'lt': 80}}  # More permissive
```


#### Issue: Asset not found

**Problem:** `KeyError: Asset 'B02' not found`

**Solutions:**

```python
# 1. Check available assets
item.print_assets_info()

# 2. Use correct naming for provider
# PC: B01, B02, B03...
# ES: coastal, blue, green...

# 3. Handle gracefully
try:
    url = item.get_asset_url('B02')
except KeyError:
    # Try alternative naming
    url = item.get_asset_url('blue')
```


#### Issue: EarthSearch datetime format

**Problem:** EarthSearch requires RFC3339 format

**Solution:**

```python
# Use proper format for EarthSearch
datetime_es = '2024-01-01T00:00:00Z/2024-03-31T23:59:59Z'

# Package handles this automatically in most cases
```


#### Issue: Large data downloads

**Problem:** Memory issues with large datasets

**Solutions:**

```python
# 1. Use overview levels (if your raster package supports it)
import rioxarray
data = rioxarray.open_rasterio(url, overview_level=2)

# 2. Use chunking
data = rioxarray.open_rasterio(url, chunks={'x': 512, 'y': 512})

# 3. Read windows
import rasterio
with rasterio.open(url) as src:
    window = rasterio.windows.Window(0, 0, 1024, 1024)
    data = src.read(1, window=window)
```


### Debug Mode

```python
# Enable debug information
import logging
logging.basicConfig(level=logging.DEBUG)

# Check what URLs are being generated
item = items[0]
print(f"Item ID: {item.id}")
print(f"Provider: {item.provider}")

all_urls = item.get_all_asset_urls()
for asset, url in all_urls.items():
    print(f"{asset}: {url[:50]}...")
```


### Validation Steps

```python
# Validate your setup
def validate_setup():
    """Validate package installation and API access."""
    try:
        import open_geodata_api as ogapi
        print("āœ… Package imported successfully")
        
        # Test client creation
        pc = ogapi.planetary_computer()
        es = ogapi.earth_search()
        print("āœ… Clients created successfully")
        
        # Test collection listing
        pc_collections = pc.list_collections()
        print(f"āœ… PC collections: {len(pc_collections)} available")
        
        # Test simple search
        test_results = pc.search(
            collections=['sentinel-2-l2a'],
            bbox=[-122.0, 47.0, -121.0, 48.0],
            limit=1
        )
        test_items = test_results.get_all_items()
        print(f"āœ… Test search: {len(test_items)} items found")
        
        return True
        
    except Exception as e:
        print(f"āŒ Validation failed: {e}")
        return False

# Run validation
validate_setup()
```


## Advanced Usage

### Custom Processing Workflows

```python
# Example: Multi-temporal analysis setup
def setup_temporal_analysis(bbox, date_ranges, max_cloud_cover=20):
    """Setup data for temporal analysis."""
    
    all_data = {}
    
    for period_name, date_range in date_ranges.items():
        print(f"Searching for {period_name}...")
        
        results = pc.search(
            collections=['sentinel-2-l2a'],
            bbox=bbox,
            datetime=date_range,
            query={'eo:cloud_cover': {'lt': max_cloud_cover}},
            limit=50
        )
        
        items = results.get_all_items()
        urls = items.get_all_urls(['B04', 'B03', 'B02', 'B08'])  # RGB + NIR
        
        all_data[period_name] = {
            'count': len(items),
            'date_range': date_range,
            'urls': urls
        }
        
        print(f"  Found {len(items)} items")
    
    return all_data

# Use for seasonal analysis
seasonal_data = setup_temporal_analysis(
    bbox=[-120.0, 35.0, -119.0, 36.0],
    date_ranges={
        'spring_2024': '2024-03-01/2024-05-31',
        'summer_2024': '2024-06-01/2024-08-31',
        'fall_2024': '2024-09-01/2024-11-30'
    }
)
```


### Integration with Other Libraries
##### Install Required Packages
```python
pip install stackstac pystac
```
##### The Custom Functions
```python
# Example: Integration with STAC-tools
def integrate_with_stac_tools(items):
    """Convert to format compatible with other STAC tools."""
    
    # Export as standard STAC format
    stac_collection = items.to_dict()  # GeoJSON FeatureCollection
    
    # Use with pystac
    try:
        import pystac
        
        # Convert items for pystac
        pystac_items = []
        for item_data in items.to_list():
            pystac_item = pystac.Item.from_dict(item_data)
            pystac_items.append(pystac_item)
        
        print(f"Converted {len(pystac_items)} items to pystac format")
        return pystac_items
        
    except ImportError:
        print("pystac not available")
        return stac_collection

# Example: Integration with stackstac
def prepare_for_stackstac(items, bands=['B04', 'B03', 'B02']):
    """Prepare data for stackstac processing."""
    
    try:
        import stackstac
        
        # Get STAC items in proper format
        stac_items = [item.to_dict() for item in items]
        
        # Note: URLs need to be properly signed
        # The package handles this automatically
        
        print(f"Prepared {len(stac_items)} items for stackstac")
        print(f"Bands: {bands}")
        
        return stac_items
        
    except ImportError:
        print("stackstac not available")
        return None

if __name__ == "__main__":
    # Use the functions
    stac_items = integrate_with_stac_tools(items)
    stackstac_items = prepare_for_stackstac(items)
    print(f"STAC items: {stac_items} \nStackSTAC items: {stackstac_items}")
    print(f"STAC items: {len(stac_items)} \nStackSTAC items: {len(stackstac_items)}")
    print("Integration and preparation complete!")
```


### Custom URL Processing

```python
# Example: Custom URL validation and processing
def process_urls_custom(items, custom_processor=None):
    """Process URLs with custom logic."""
    
    def default_processor(url):
        """Default URL processor."""
        # Add custom headers, caching, etc.
        return url
    
    processor = custom_processor or default_processor
    
    processed_urls = {}
    
    for item in items:
        item_urls = item.get_all_asset_urls()
        processed_item_urls = {}
        
        for asset, url in item_urls.items():
            processed_url = processor(url)
            processed_item_urls[asset] = processed_url
        
        processed_urls[item.id] = processed_item_urls
    
    return processed_urls

# Example custom processor
def add_caching_headers(url):
    """Add caching parameters to URL."""
    if '?' in url:
        return f"{url}&cache=3600"
    else:
        return f"{url}?cache=3600"

# Use custom processing
cached_urls = process_urls_custom(items, add_caching_headers)
print(f"Cached URLs: {cached_urls}")
```
## Utils Functions

### Utils Functions - Usage Examples

```python
import open_geodata_api as ogapi
from open_geodata_api.utils import (
    filter_by_cloud_cover,
    download_datasets,
    download_url,
    download_from_json,
    download_seasonal,
    download_single_file,
    download_url_dict,
    download_items,
    download_seasonal_data,
    create_download_summary,
    is_url_expired,
    is_signed_url,
    re_sign_url_if_needed
)

# Setup clients
pc = ogapi.planetary_computer(auto_sign=True)
es = ogapi.earth_search()
```

#### Example 1: Complete Workflow - Search and Filter by Cloud Cover
```python
print("šŸ” Searching for Sentinel-2 data...")
results = pc.search(
    collections=["sentinel-2-l2a"],
    bbox=[-122.5, 47.5, -122.0, 48.0],  # Seattle area
    datetime="2024-06-01/2024-08-31",
    limit=20
)

items = results.get_all_items()
print(f"Found {len(items)} items")

# Filter by cloud cover using utils
clear_items = filter_by_cloud_cover(items, max_cloud_cover=15)
print(f"After filtering: {len(clear_items)} clear items (<15% clouds)")
```

#### Example 2: Download Single Asset from Search Results
```python
print("\nšŸ“„ Downloading single asset...")
first_item = clear_items[^0]
first_item.print_assets_info()

# Get a single band URL and download
red_url = first_item.get_asset_url('B04')  # Red band, auto-signed
downloaded_file = download_single_file(
    red_url, 
    destination="./data/red_band.tif",
    provider="planetary_computer"
)
print(f"Downloaded: {downloaded_file}")
```

#### Example 3: Download RGB Bands from Multiple Items
```python
print("\nšŸŽØ Downloading RGB bands from multiple items...")
rgb_downloads = download_items(
    clear_items[:3],  # First 3 clear items
    base_destination="./rgb_data/",
    asset_keys=['B04', 'B03', 'B02'],  # Red, Green, Blue
    create_product_folders=True
)

print(f"Downloaded RGB data for {len(rgb_downloads)} items")
```

#### Example 4: Multi-Provider Data Collection and Download
```python
print("\nšŸŒ Comparing data from multiple providers...")

# Search both providers
search_params = {
    'collections': ['sentinel-2-l2a'],
    'bbox': [-120.0, 35.0, -119.0, 36.0],  # California
    'datetime': '2024-07-01/2024-07-31',
    'limit': 5
}

pc_results = pc.search(**search_params)
es_results = es.search(**search_params)

pc_items = pc_results.get_all_items()
es_items = es_results.get_all_items()

print(f"PC found: {len(pc_items)} items")
print(f"ES found: {len(es_items)} items")

# Filter both collections
pc_clear = filter_by_cloud_cover(pc_items, max_cloud_cover=20)
es_clear = filter_by_cloud_cover(es_items, max_cloud_cover=20)

# Download from both providers
print("šŸ“¦ Downloading from Planetary Computer...")
pc_downloads = download_items(
    pc_clear[:2], 
    base_destination="./pc_data/",
    asset_keys=['B08', 'B04'],  # NIR, Red for NDVI
)

print("šŸ“¦ Downloading from EarthSearch...")
es_downloads = download_items(
    es_clear[:2], 
    base_destination="./es_data/",
    asset_keys=['nir', 'red'],  # ES naming convention
)
```

#### Example 5: Seasonal Analysis Workflow
```python
print("\n🌱 Setting up seasonal analysis...")

def collect_seasonal_data(bbox, year):
    """Collect data for seasonal analysis."""
    seasons = {
        'spring': f'{year}-03-01/{year}-05-31',
        'summer': f'{year}-06-01/{year}-08-31', 
        'fall': f'{year}-09-01/{year}-11-30',
        'winter': f'{year}-12-01/{year+1}-02-28'
    }
    
    seasonal_data = {}
    
    for season, date_range in seasons.items():
        print(f"šŸ” Searching {season} {year} data...")
        
        results = pc.search(
            collections=['sentinel-2-l2a'],
            bbox=bbox,
            datetime=date_range,
            query={'eo:cloud_cover': {'lt': 25}},
            limit=10
        )
        
        items = results.get_all_items()
        filtered_items = filter_by_cloud_cover(items, max_cloud_cover=20)
        
        # Get URLs for NDVI calculation
        urls = filtered_items.get_all_urls(['B08', 'B04'])  # NIR, Red
        
        seasonal_data[season] = {
            'count': len(filtered_items),
            'date_range': date_range,
            'urls': urls
        }
        
        print(f"  Found {len(filtered_items)} clear scenes")
    
    return seasonal_data

# Collect seasonal data
bbox = [-121.0, 38.0, -120.5, 38.5]  # Northern California
seasonal_data = collect_seasonal_data(bbox, 2024)

# Download seasonal data using utils
seasonal_downloads = download_seasonal_data(
    seasonal_data,
    base_destination="./seasonal_analysis/",
    seasons=['spring', 'summer'],  # Only spring and summer
    asset_keys=['B08', 'B04']  # NIR and Red bands
)
```

#### Example 6: URL Management and Re-signing
```python
print("\nšŸ” URL management example...")

# Get some URLs from items
item = pc_items[^0] if pc_items else clear_items[^0]
all_urls = item.get_all_asset_urls()

# Check URL status
for asset, url in list(all_urls.items())[:3]:
    print(f"\nšŸ”— Asset: {asset}")
    print(f"   Signed: {is_signed_url(url)}")
    print(f"   Expired: {is_url_expired(url)}")
    
    # Re-sign if needed
    fresh_url = re_sign_url_if_needed(url, provider="planetary_computer")
    if fresh_url != url:
        print(f"   āœ… URL was re-signed")
```

#### Example 7: Batch Processing with URL Dictionary
```python
print("\nšŸ“Š Batch processing workflow...")

# Create a custom URL dictionary from search results
custom_urls = {}
for i, item in enumerate(clear_items[:3]):
    item_id = f"sentinel2_{item.id[-8:]}"  # Shortened ID
    # Get specific bands for analysis
    item_urls = item.get_band_urls(['B02', 'B03', 'B04', 'B08'])
    custom_urls[item_id] = item_urls

print(f"Created custom URL dictionary with {len(custom_urls)} items")

# Download using URL dictionary
batch_downloads = download_url_dict(
    {k: v for k, v in list(custom_urls.items())[^0].items()},  # First item only
    base_destination="./batch_data/",
    provider="planetary_computer",
    create_subfolders=True
)
```
#### Example 8: Export and Import Workflow
```python
print("\nšŸ’¾ Export/Import workflow...")

# Export URLs to JSON for later processing
import json
with open('./data_urls.json', 'w') as f:
    json.dump(custom_urls, f, indent=2)

print("šŸ“¤ URLs exported to data_urls.json")

# Download from JSON file using utils
json_downloads = download_from_json(
    './data_urls.json',
    destination="./from_json/",
    asset_keys=['B04', 'B08'],  # Only specific bands
    create_folders=True
)
```

#### Example 9: Download Summary and Reporting
```python
print("\nšŸ“‹ Creating download summary...")

# Combine all download results
all_downloads = {
    'rgb_downloads': rgb_downloads,
    'pc_downloads': pc_downloads,
    'es_downloads': es_downloads,
    'seasonal_downloads': seasonal_downloads,
    'batch_downloads': batch_downloads,
    'json_downloads': json_downloads
}

# Create comprehensive summary
summary = create_download_summary(
    all_downloads, 
    output_file="./download_report.json"
)

print(f"šŸ“Š Download Summary:")
print(f"   Total files: {summary['total_files']}")
print(f"   Successful: {summary['successful_downloads']}")
print(f"   Failed: {summary['failed_downloads']}")
print(f"   Success rate: {summary['success_rate']}")
```

#### Example 10: Advanced Filtering and Processing
```python
print("\nšŸ”¬ Advanced processing workflow...")

# Multi-step filtering
def advanced_processing_workflow(bbox, max_cloud=10):
    """Advanced workflow with multiple filtering steps."""
    
    # Step 1: Search with broader criteria
    results = pc.search(
        collections=['sentinel-2-l2a'],
        bbox=bbox,
        datetime='2024-06-01/2024-09-30',
        limit=50
    )
    
    items = results.get_all_items()
    print(f"Step 1: Found {len(items)} total items")
    
    # Step 2: Filter by cloud cover
    clear_items = filter_by_cloud_cover(items, max_cloud_cover=max_cloud)
    print(f"Step 2: {len(clear_items)} items with <{max_cloud}% clouds")
    
    # Step 3: Convert to DataFrame for advanced filtering
    df = clear_items.to_dataframe(include_geometry=False)
    
    # Step 4: Filter by date (summer months only)
    summer_mask = df['datetime'].str.contains('2024-0[^678]')  # June, July, August
    summer_items_ids = df[summer_mask]['id'].tolist()
    
    # Step 5: Get items for summer period
    summer_items = [item for item in clear_items if item.id in summer_items_ids]
    print(f"Step 3: {len(summer_items)} summer items")
    
    # Step 6: Download analysis-ready data
    analysis_downloads = download_items(
        summer_items[:5],  # Top 5 summer items
        base_destination="./analysis_ready/",
        asset_keys=['B02', 'B03', 'B04', 'B08', 'B11', 'B12'],  # Multi-spectral
        create_product_folders=True
    )
    
    return analysis_downloads, summer_items

# Run advanced workflow
analysis_results, summer_items = advanced_processing_workflow(
    bbox=[-122.0, 37.0, -121.5, 37.5],  # San Francisco Bay
    max_cloud=5
)

print(f"āœ… Analysis-ready data downloaded for {len(analysis_results)} items")
```

#### Example 11: Error Handling and Resilient Downloads
```python
print("\nšŸ›”ļø Resilient download example...")

def resilient_download(items, max_retries=3):
    """Download with retry logic and error handling."""
    
    successful_downloads = {}
    failed_downloads = {}
    
    for item in items[:2]:  # Process first 2 items
        item_id = item.id
        retries = 0
        
        while retries < max_retries:
            try:
                # Try to download key bands
                downloads = download_items(
                    [item],
                    base_destination=f"./resilient_data/attempt_{retries+1}/",
                    asset_keys=['B04', 'B08'],
                    create_product_folders=True
                )
                
                successful_downloads[item_id] = downloads
                print(f"āœ… Successfully downloaded {item_id}")
                break
                
            except Exception as e:
                retries += 1
                print(f"āŒ Attempt {retries} failed for {item_id}: {e}")
                
                if retries >= max_retries:
                    failed_downloads[item_id] = str(e)
                    print(f"šŸ’€ Gave up on {item_id} after {max_retries} attempts")
    
    return successful_downloads, failed_downloads

# Run resilient download
successful, failed = resilient_download(clear_items)
print(f"Resilient download completed: {len(successful)} successful, {len(failed)} failed")

print("\nšŸŽ‰ All utils function examples completed!")
print(f"Check your './data/' directory for downloaded files")
```

## CLI Usage

### Command Line Interface (CLI) Usage
Open Geodata API provides a comprehensive CLI for satellite data discovery, filtering, and downloading. After installation, use the `ogapi` command to access all functionality.

#### Show package information
```bash
ogapi info
```

#### Get help for any command
```bash
ogapi --help
```
```bash
ogapi collections --help
```
```bash
ogapi search items --help
```
### Collections Management
#### List all collections from both providers
```bash
ogapi collections list
```
#### List from specific provider
```bash
ogapi collections list --provider pc
```
```bash
ogapi collections list --provider es
```

#### Filter collections by keyword
```bash
ogapi collections list --filter sentinel
```

#### Save results to file
```bash
ogapi collections list --output collections.json
```

### Search Collections
#### Find collections by keyword
```bash
ogapi collections search sentinel
```
```bash
ogapi collections search landsat --provider pc
```
```bash
ogapi collections search modis --provider both
```

### Get Collection Information
#### Get detailed collection info
```bash
ogapi collections info sentinel-2-l2a
```
```bash
ogapi collections info sentinel-2-l2a --provider es
```
```bash
ogapi collections info landsat-c2-l2 --output collection_info.json
```

### Data Search
#### Search for Sentinel-2 data in Seattle area
```bash
ogapi search items --collections sentinel-2-l2a --bbox "-122.5,47.5,-122.0,48.0" --datetime "2024-06-01/2024-08-31" --limit 10
```

#### Search with cloud cover filter
```bash
ogapi search items --collections sentinel-2-l2a --bbox "-122.5,47.5,-122.0,48.0" --cloud-cover 20 --output search_results.json
```

#### Search multiple collections
```bash
ogapi search items --collections "sentinel-2-l2a,landsat-c2-l2" --bbox "-120.0,35.0,-119.0,36.0" --datetime "2024-01-01/2024-12-31"
```
#### Advanced Search with JSON Query
```bash
ogapi search items --collections sentinel-2-l2a --bbox "-122.5,47.5,-122.0,48.0" --query '{"eo:cloud_cover":{"lt":15},"platform":{"eq":"sentinel-2a"}}' --output advanced_search.json
```

#### Find recent clear data (last 30 days)
```bash
ogapi search quick sentinel-2-l2a "-122.5,47.5,-122.0,48.0"
```

#### Customize time range and cloud threshold
```bash
ogapi search quick sentinel-2-l2a "-122.5,47.5,-122.0,48.0" --days 7 --cloud-cover 10 --limit 5
```

#### Save results for later processing
```bash
ogapi search quick landsat-c2-l2 "-120.0,35.0,-119.0,36.0" --output recent_landsat.json
```

### Compare Data Availability Between Providers
#### Compare Sentinel-2 availability
```bash
ogapi search compare --collections sentinel-2-l2a --bbox "-122.5,47.5,-122.0,48.0" --datetime "2024-06-01/2024-08-31"
```
#### Compare with cloud filtering
```bash
ogapi search compare --collections sentinel-2-l2a --bbox "-122.5,47.5,-122.0,48.0" --cloud-cover 25 --output comparison_results.json
```

### Working with Items
#### Show info for first item in search results
```bash
ogapi items info search_results.json
```
#### Show info for specific item by index
```bash
ogapi items info search_results.json --item-index 2
```
#### Show detailed metadata
```bash
ogapi items info search_results.json --show-all --output item_details.json
```
#### List all assets for an item
```bash
ogapi items assets search_results.json
```
#### Filter assets by pattern
```bash
ogapi items assets search_results.json --type "image/tiff"
```
#### Show URLs for assets
```bash
ogapi items assets search_results.json --show-urls
```
#### Get URLs for RGB bands
```bash
ogapi items urls search_results.json --assets "B04,B03,B02"
```
#### Get all asset URLs
```bash
ogapi items urls search_results.json --output all_urls.json
```
#### Get URLs by pattern
```bash
ogapi items urls search_results.json --pattern "B0" --output optical_bands.json
```
#### Get unsigned URLs
```bash
ogapi items urls search_results.json --unsigned
```
### Compare Items by Quality
#### Compare items by cloud cover (find clearest)
```bash
ogapi items compare search_results.json
```
#### Compare by date (find most recent)
```bash
ogapi items compare search_results.json --metric date
```
#### Compare asset availability
```bash
ogapi items compare search_results.json --metric assets --max-items 10
```
### Data Download
#### Download all assets from search results
```bash
ogapi download search-results search_results.json
```
#### Download specific bands only
```bash
ogapi download search-results search_results.json --assets "B04,B03,B02" --destination "./rgb_data/"
```
#### Download with additional filtering
```bash
ogapi download search-results search_results.json --cloud-cover 15 --max-items 5 --assets "B08,B04"
```
#### Create flat file structure
```bash
ogapi download search-results search_results.json --flat-structure --destination "./satellite_data/
```
#### Download single file
```bash
ogapi download url "https://example.com/sentinel2_B04.tif"
```
#### Download to specific location
```bash
ogapi download url "https://example.com/B04.tif" --destination "./data/red_band.tif"
```
#### Download with provider specification (it will handdle url validation)
```bash
ogapi download url "https://pc.example.com/B04.tif" --provider pc
```
#### Download from exported URLs
```bash
ogapi download urls-json exported_urls.json
```
#### Custom destination
```bash
ogapi download urls-json urls.json --destination "./downloads/" --flat-structure
```
#### Download all seasons from seasonal JSON
```bash
ogapi download seasonal seasonal_data.json
```
#### Download specific seasons and assets
```bash
ogapi download seasonal seasonal_data.json --seasons "spring,summer" --assets "B08,B04" --destination "./time_series/"
```
### Filter by Cloud Cover
#### Filter search results by cloud cover
```bash
ogapi utils filter-clouds search_results.json --max-cloud-cover 20
```
#### Filter and save results
```bash
ogapi utils filter-clouds search_results.json --max-cloud-cover 15 --output clear_results.json --show-stats
```
### Export URLs
#### Export all URLs from search results
```bash
ogapi utils export-urls search_results.json --output all_urls.json
```
#### Export specific assets
```bash
ogapi utils export-urls search_results.json --output rgb_urls.json --assets "B04,B03,B02"
```
#### Export in simple format
```bash
ogapi utils export-urls search_results.json --output simple_urls.json --format simple
```
#### Export unsigned URLs
```bash
ogapi utils export-urls search_results.json --output unsigned_urls.json --unsigned
```
### Validate URLs
#### Basic URL validation
```bash
ogapi utils validate-urls urls.json
```
#### Check accessibility (HTTP requests)
```bash
ogapi utils validate-urls urls.json --check-access
```
#### Fix expired URLs
```bash
ogapi utils validate-urls urls.json --fix-expired --output fixed_urls.json
```
#### Skip expiry check for speed
```bash
ogapi utils validate-urls urls.json --no-check-expiry
```
### Analyze Search Results
#### Analyze cloud cover distribution
```bash
ogapi utils analyze search_results.json
```
#### Temporal analysis
```bash
ogapi utils analyze search_results.json --metric temporal
```
#### Asset availability analysis
```bash
ogapi utils analyze search_results.json --metric assets --output analysis_report.json
```
### Create Download Summaries
#### Create detailed download summary
```bash
ogapi utils download-summary download_results.json
```
#### Brief summary
```bash
ogapi utils download-summary results.json --format brief
```
#### Save summary report
```bash
ogapi utils download-summary results.json --output report.json
```
### Complete Workflow Examples
#### 1.1: Search for data (Basic RGB Download Workflow)
```bash
ogapi search items --collections sentinel-2-l2a --bbox "-122.5,47.5,-122.0,48.0" --datetime "2024-06-01/2024-08-31" --cloud-cover 20 --output search_results.json
```
#### 1.2: Filter for very clear imagery
```bash
ogapi utils filter-clouds search_results.json --max-cloud-cover 10 --output clear_results.json
```
#### 1.3: Download RGB bands
```bash
ogapi download search-results clear_results.json --assets "B04,B03,B02" --destination "./rgb_analysis/"
```
#### 1.4: Create summary
```bash
ogapi utils download-summary download_results.json
```
#### 2.1: Search for data (NDVI Analysis Workflow)
```bash
ogapi search items --collections sentinel-2-l2a --bbox "-120.0,35.0,-119.0,36.0" --datetime "2024-01-01/2024-12-31" --cloud-cover 25 --output yearly_search.json
```
#### 2.2: Filter by seasons and export URLs
```bash
ogapi utils filter-clouds yearly_search.json --max-cloud-cover 15 --output clear_yearly.json
```
```bash
ogapi utils export-urls clear_yearly.json --assets "B08,B04" --output ndvi_urls.json
```
#### 2.3: Download NDVI bands
```bash
ogapi download urls-json ndvi_urls.json --destination "./ndvi_analysis/"
```
#### 3.1 Compare data availability (Multi-Provider Comparison)
```bash
ogapi search compare --collections sentinel-2-l2a --bbox "-122.5,47.5,-122.0,48.0" --datetime "2024-06-01/2024-08-31" --output comparison.json
```
#### 3.2 Search both providers separately
```bash
ogapi search items --provider pc --collections sentinel-2-l2a --bbox "-122.5,47.5,-122.0,48.0" --cloud-cover 20 --output pc_results.json
```
```bash
ogapi search items --provider es --collections sentinel-2-l2a --bbox "-122.5,47.5,-122.0,48.0" --cloud-cover 20 --output es_results.json
```
#### 3.3 Download from best provider
```bash
ogapi download search-results pc_results.json --max-items 3 --destination "./pc_data/"
```
#### 4.1 Search and analyze (Quality Assessment Workflow) 
```bash
ogapi search items --collections sentinel-2-l2a --bbox "-122.5,47.5,-122.0,48.0" --limit 50 --output large_search.json
```
#### 4.2 Analyze data quality 
```bash
ogapi utils analyze large_search.json --metric cloud_cover
```
```bash
ogapi utils analyze large_search.json --metric temporal
```
```bash
ogapi utils analyze large_search.json --metric assets
```
#### 4.3 Compare individual items
```bash
ogapi items compare large_search.json --metric cloud_cover
```
#### 4.4 Download best items only
```bash
ogapi utils filter-clouds large_search.json --max-cloud-cover 10 --output best_quality.json
```
```bash
ogapi download search-results best_quality.json --max-items 5
```
### Global Options
**All commands support these global options:**
#### Enable verbose output for debugging
```bash
ogapi --verbose [command]
```
#### Show version
```bash
ogapi --version
```
#### Get help for any command
```bash
ogapi [command] --help
```
```bash
ogapi [command] [subcommand] --help
```

### Tips and Best Practices

1. **Start Small**: Use `--limit` and `--max-items` to test workflows before large downloads
2. **Save Results**: Always use `--output` to save search results for reprocessing
3. **Filter Early**: Use cloud cover filters to reduce data volume
4. **Check Status**: Use validation commands before large downloads
5. **Resume Downloads**: Most download commands support resuming interrupted transfers
6. **Use Verbose Mode**: Add `--verbose` for detailed debugging information

### Environment Variables
#### Optional: Set default provider
```bash
export OGAPI_DEFAULT_PROVIDER=pc
```
#### Optional: Set default destination
```bash
export OGAPI_DEFAULT_DEST=./satellite_data/
```

## FAQ

### General Questions

**Q: What makes this package different from using APIs directly?**

A: Key advantages:

- Unified interface across multiple APIs
- Automatic URL signing/validation
- Consistent error handling
- No lock-in to specific data reading packages
- Built-in best practices

**Q: Can I use this with my existing geospatial workflow?**

A: Absolutely! The package provides URLs that work with any raster reading library:

```python
url = item.get_asset_url('red')

# Use with your existing tools
import rioxarray; data = rioxarray.open_rasterio(url)
import rasterio; data = rasterio.open(url)
from osgeo import gdal; data = gdal.Open(url)
```

**Q: Do I need API keys?**

A: Only for Planetary Computer. EarthSearch is completely open.

### Technical Questions

**Q: How does automatic URL signing work?**

A: When `auto_sign=True`, the package:

1. Detects the provider (PC vs ES)
2. For PC: Uses the planetary-computer package to sign URLs
3. For ES: Returns URLs as-is (no signing needed)
4. You can override with `signed=False/True`

**Q: What about rate limiting?**

A: Both APIs have rate limits:

- **Planetary Computer**: Generous limits for signed URLs
- **EarthSearch**: Standard HTTP rate limits

The package doesn't implement rate limiting - use your own if needed.

**Q: Can I cache results?**

A: Yes, several approaches:

```python
# 1. Export URLs to JSON
items.export_urls_json('cache.json')

# 2. Save DataFrames
df = items.to_dataframe()
df.to_parquet('metadata_cache.parquet')

# 3. Use your own caching layer
```

**Q: How do I handle different projections?**

A: The package provides URLs - projection handling is up to your raster library:

```python
import rioxarray
data = rioxarray.open_rasterio(url)
data_reprojected = data.rio.reproject('EPSG:4326')
```


### Troubleshooting Questions

**Q: Why am I getting "Asset not found" errors?**

A: Different providers use different asset names:

- **PC**: B01, B02, B03, B04...
- **EarthSearch**: coastal, blue, green, red...

Use `item.print_assets_info()` to see available assets.

**Q: Search returns no results but data should exist**

A: Common issues:

1. **Bbox order**: Use [west, south, east, north]
2. **Date format**: PC accepts "YYYY-MM-DD", ES prefers RFC3339
3. **Collection names**: Use `client.list_collections()` to verify
4. **Cloud cover**: Try relaxing the threshold

**Q: URLs work but data loading is slow**

A: Optimization strategies:

1. Use overview levels: `rioxarray.open_rasterio(url, overview_level=2)`
2. Enable chunking: `rioxarray.open_rasterio(url, chunks=True)`
3. Read smaller windows with rasterio
4. Consider geographic proximity to data

### Integration Questions

**Q: Can I use this with Jupyter notebooks?**

A: Yes! The package works great in Jupyter:

```python
# Display asset info
item.print_assets_info()

# Show DataFrames
df = items.to_dataframe()
display(df)

# Plot with matplotlib/cartopy
import matplotlib.pyplot as plt
data = rioxarray.open_rasterio(url)
data.plot()
```

**Q: How do I integrate with QGIS/ArcGIS?**

A: Export URLs and use them directly:

```python
# Get URLs
urls = item.get_all_asset_urls()

# In QGIS: Add Raster Layer -> use the URL directly
# In ArcGIS: Add Data -> Raster Dataset -> paste URL
```

**Q: Can I use this in production systems?**

A: Yes! The package is designed for production use:

- Robust error handling
- No forced dependencies
- Clean separation of concerns
- Comprehensive logging support

**Q: How do I contribute or report issues?**

A: Visit the GitHub repository:

- Report issues: GitHub Issues
- Contribute: Pull Requests welcome
- Documentation: Help improve this guide

---

This completes the comprehensive user guide for Open Geodata API. The package provides a clean, flexible foundation for accessing open geospatial data while letting you maintain full control over data processing and analysis workflows.

--- End ---

[^1]: https://developers.arcgis.com/python/latest/guide/tutorials/import-data/

[^2]: https://github.com/geopython/pygeoapi/blob/master/pygeoapi/openapi.py

[^3]: https://opencagedata.com/api

[^4]: https://opencagedata.com/tutorials/geocode-in-python

[^5]: https://guides.library.columbia.edu/geotools/Python

[^6]: https://pygeoapi.io

[^7]: https://live.osgeo.org/en/quickstart/pygeoapi_quickstart.html

[^8]: https://packaging.python.org

[^9]: [http://r-project.ro/conference2018/presentations/Tutorial_Spatial_Analysis_in_R_with_Open_Geodata_-_uRos2018.pdf](https://github.com/Mirjan-Ali-Sha/open-geodata-api/blob/main/Open%20Geodata%20API%20-%20Complete%20User%20Guide.pdf)

[^10]: https://geodata.readthedocs.io


            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "open-geodata-api",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "api-client, cli, earth-observation, earthsearch, geodata, geospatial, open-data, planetary-computer, remote-sensing, satellite, stac",
    "author": null,
    "author_email": "Mirjan Ali Sha <mastools.help@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/88/4a/f6e353404bb9fc0cd12fdaf5bb4bc2efaf70c35820a30462f699d7e562eb/open_geodata_api-0.3.0.tar.gz",
    "platform": null,
    "description": "<p align=\"center\">\n  <img src=\"https://github.com/Mirjan-Ali-Sha/open-geodata-api/blob/main/icon.png\" alt=\"Open Geodata API Icon\" width=\"150\" height=\"150\" />\n</p>\n\n# Open Geodata API - Complete User Guide\n\n## Table of Contents\n\n1. [Introduction](README.md#introduction)\n2. [Open Geodata API - Complete Tool Summary](README.md#open-geodata-api---complete-tool-summary)\n3. [Installation](README.md#installation)\n4. [Quick Start](README.md#quick-start)\n5. [Core Concepts](README.md#core-concepts)\n6. [API Reference](README.md#api-reference)\n7. [Usage Examples](README.md#usage-examples)\n8. [Best Practices](README.md#best-practices)\n9. [Troubleshooting](README.md#troubleshooting)\n10. [Advanced Usage](README.md#advanced-usage)\n11. [Utility Functions](README.md#utils-functions)\n12. [CLI Usage](README.md#cli-usage)\n13. [FAQ](README.md#faq)\n\n## Introduction\n\n### What is Open Geodata API?\n\n**Open Geodata API** is a unified Python client library that provides seamless access to multiple open geospatial data APIs. It focuses on **API access, search, and URL management** while maintaining maximum flexibility for data reading and processing.\n\n## Open Geodata API - Complete Tool Summary\n\n**\ud83d\udef0\ufe0f Unified Python Client for Satellite Data Access**\n\n#### **What It Does**\n\n\u2705 **One Interface** - Access Microsoft Planetary Computer \\& AWS EarthSearch APIs<br>\n\u2705 **Smart URLs** - Automatic signing, validation, and expiration handling<br>\n\u2705 **Your Choice** - Use any raster package (rioxarray, rasterio, GDAL)<br>\n\u2705 **Complete Workflow** - Search \u2192 Filter \u2192 Download \u2192 Analyze\n\n#### **Key Capabilities**\n\n\u2705 **Python API** - Programmatic access with full flexibility<br>\n\u2705 **Command Line** - `ogapi` CLI for all operations with help<br>\n\u2705 **Smart Downloads** - Progress tracking, resume, batch processing<br>\n\u2705 **Data Filtering** - Cloud cover, temporal, quality-based filtering<br>\n\u2705 **URL Management** - Export, validate, and refresh URLs automatically\n\n\n### Supported APIs\n\n| API | Provider | Authentication | URL Handling |\n| :-- | :-- | :-- | :-- |\n| **Planetary Computer** | Microsoft | API Key + Signing | Automatic signing |\n| **EarthSearch** | Element84/AWS | None required | URL validation |\n\n### Philosophy\n\n\ud83c\udfaf **Core Focus**: We provide URLs - you choose how to read them!<br>\n\ud83d\udce6 **Use Any Package**: rioxarray, rasterio, GDAL, or any package you prefer<br>\n\ud83d\ude80 **Maximum Flexibility**: Zero restrictions on your workflow\n\n## Installation\n\n### Basic Installation\n\n```bash\n# Install core package\npip install open-geodata-api\n```\n\n\n### Optional Dependencies\n\n```bash\n# For spatial analysis (shapely, geopandas)\npip install open-geodata-api[spatial]\n\n# For raster reading suggestions (rioxarray,rasterio, xarray)\npip install open-geodata-api[io]  # rioxarray + xarray\n\n# For complete examples (shapely, geopandas, rioxarray, rasterio, xarray)\npip install open-geodata-api[complete]\n\n# Development dependencies\npip install open-geodata-api[dev]\n```\n\n\n### Verify Installation\n\n```python\nimport open_geodata_api as ogapi\nogapi.info()\n```\n\n\n## Quick Start\n\n### 30-Second Example\n\n```python\nimport open_geodata_api as ogapi\n\n# Get clients for both APIs\nclients = ogapi.get_clients(pc_auto_sign=True)\npc = clients['planetary_computer']\nes = clients['earth_search']\n\n# Search for Sentinel-2 data\nresults = pc.search(\n    collections=[\"sentinel-2-l2a\"],\n    bbox=[-122.5, 47.5, -122.0, 48.0],\n    datetime=\"2024-01-01/2024-03-31\"\n)\n\n# Get items and URLs\nitems = results.get_all_items()\nitem = items[0]\n\n# Get ready-to-use URLs\nblue_url = item.get_asset_url('B02')  # Automatically signed!\nall_urls = item.get_all_asset_urls()  # All assets\n\n# Use with ANY raster package\nimport rioxarray\ndata = rioxarray.open_rasterio(blue_url)\n\n# Or use with rasterio\nimport rasterio\nwith rasterio.open(blue_url) as src:\n    data = src.read(1)\n```\n\n\n### 5-Minute Tutorial\n\n```python\n# 1. Import and setup\nimport open_geodata_api as ogapi\n\n# 2. Create clients\npc = ogapi.planetary_computer(auto_sign=True)\nes = ogapi.earth_search()\n\n# 3. Search for data\nsearch_params = {\n    'collections': ['sentinel-2-l2a'],\n    'bbox': [-122.5, 47.5, -122.0, 48.0],\n    'datetime': '2024-01-01/2024-03-31',\n    'query': {'eo:cloud_cover': {'lt': 30}}\n}\n\npc_results = pc.search(**search_params, limit=10)\nes_results = es.search(**search_params, limit=10)\n\n# 4. Work with results\npc_items = pc_results.get_all_items()\nes_items = es_results.get_all_items()\n\nprint(f\"Found: PC={len(pc_items)}, ES={len(es_items)} items\")\n\n# 5. Get URLs and use with your preferred package\nitem = pc_items[0]\nitem.print_assets_info()\n\n# Get specific bands\nrgb_urls = item.get_band_urls(['B04', 'B03', 'B02'])  # Red, Green, Blue\nprint(f\"RGB URLs: {rgb_urls}\")\n\n# Use URLs with any package you want!\n```\n\n\n## Core Concepts\n\n### STAC (SpatioTemporal Asset Catalog)\n\nOpen Geodata API works with STAC-compliant APIs. Key STAC concepts:\n\n- **Collections**: Groups of related datasets (e.g., \"sentinel-2-l2a\")\n- **Items**: Individual products/scenes with metadata\n- **Assets**: Individual files (bands, thumbnails, metadata)\n\n\n### Package Architecture\n\n```\nopen-geodata-api/\n\u251c\u2500\u2500 Core Classes (Universal)\n\u2502   \u251c\u2500\u2500 STACItem           # Individual products\n\u2502   \u251c\u2500\u2500 STACItemCollection # Groups of products  \n\u2502   \u251c\u2500\u2500 STACAsset          # Individual files\n\u2502   \u2514\u2500\u2500 STACSearch         # Search results\n\u251c\u2500\u2500 API Clients\n\u2502   \u251c\u2500\u2500 PlanetaryComputerCollections\n\u2502   \u2514\u2500\u2500 EarthSearchCollections\n\u2514\u2500\u2500 Utilities\n    \u251c\u2500\u2500 URL signing (PC)\n    \u251c\u2500\u2500 URL validation (ES)\n    \u2514\u2500\u2500 Filtering functions\n```\n\n\n### Provider-Specific Handling\n\n| Feature | Planetary Computer | EarthSearch |\n| :-- | :-- | :-- |\n| **Authentication** | Automatic via planetary-computer package | None required |\n| **URL Signing** | Automatic (auto_sign=True) | Not applicable |\n| **Asset Naming** | B01, B02, B03... | coastal, blue, green... |\n| **Cloud Cover** | eo:cloud_cover | eo:cloud_cover |\n\n## API Reference\n\n### Factory Functions\n\n#### `planetary_computer(auto_sign=False)`\n\nCreates a Planetary Computer client.\n\n**Parameters:**\n\n- `auto_sign` (bool): Automatically sign URLs for immediate use\n\n**Returns:** `PlanetaryComputerCollections` instance\n\n#### `earth_search(auto_validate=False)`\n\nCreates an EarthSearch client.\n\n**Parameters:**\n\n- `auto_validate` (bool): Validate URLs (currently placeholder)\n\n**Returns:** `EarthSearchCollections` instance\n\n#### `get_clients(pc_auto_sign=False, es_auto_validate=False)`\n\nCreates both clients simultaneously.\n\n**Returns:** Dictionary with 'planetary_computer' and 'earth_search' keys\n\n### Client Methods\n\n#### `search(collections, bbox=None, datetime=None, query=None, limit=100)`\n\nSearch for STAC items.\n\n**Parameters:**\n\n- `collections` (list): Collection IDs to search\n- `bbox` (list): Bounding box [west, south, east, north]\n- `datetime` (str): Date range \"YYYY-MM-DD/YYYY-MM-DD\"\n- `query` (dict): Additional filters like `{\"eo:cloud_cover\": {\"lt\": 30}}`\n- `limit` (int): Maximum results to return\n\n**Returns:** `STACSearch` instance\n\n#### `list_collections()`\n\nGet list of available collection names.\n\n**Returns:** List of collection ID strings\n\n#### `get_collection_info(collection_name)`\n\nGet detailed information about a specific collection.\n\n**Returns:** Collection metadata dictionary\n\n### STACItem Methods\n\n#### `get_asset_url(asset_key, signed=None)`\n\nGet ready-to-use URL for a specific asset.\n\n**Parameters:**\n\n- `asset_key` (str): Asset name (e.g., 'B02', 'blue', 'red')\n- `signed` (bool): Override automatic signing behavior\n\n**Returns:** URL string ready for any raster package\n\n#### `get_all_asset_urls(signed=None)`\n\nGet URLs for all available assets.\n\n**Returns:** Dictionary `{asset_key: url}`\n\n#### `get_band_urls(bands, signed=None)`\n\nGet URLs for specific bands/assets.\n\n**Parameters:**\n\n- `bands` (list): List of asset names\n\n**Returns:** Dictionary `{asset_key: url}`\n\n#### `list_assets()`\n\nGet list of available asset names.\n\n**Returns:** List of asset key strings\n\n#### `print_assets_info()`\n\nPrint detailed information about all assets.\n\n### STACItemCollection Methods\n\n#### `get_all_urls(asset_keys=None, signed=None)`\n\nGet URLs from all items in the collection.\n\n**Parameters:**\n\n- `asset_keys` (list, optional): Specific assets to get URLs for\n- `signed` (bool, optional): Override signing behavior\n\n**Returns:** Dictionary `{item_id: {asset_key: url}}`\n\n#### `to_dataframe(include_geometry=True)`\n\nConvert collection to pandas/geopandas DataFrame.\n\n**Parameters:**\n\n- `include_geometry` (bool): Include spatial geometry (requires geopandas)\n\n**Returns:** DataFrame with item metadata\n\n#### `export_urls_json(filename, asset_keys=None)`\n\nExport all URLs to JSON file for external processing.\n\n## Usage Examples\n\n### Example 1: Simple Data Discovery\n\n```python\nimport open_geodata_api as ogapi\n\n# Setup\npc = ogapi.planetary_computer(auto_sign=True)\n\n# Find available collections\ncollections = pc.list_collections()\nsentinel_collections = [c for c in collections if 'sentinel' in c.lower()]\nprint(f\"Sentinel collections: {sentinel_collections}\")\n\n# Get collection details\ns2_info = pc.get_collection_info('sentinel-2-l2a')\nprint(f\"Sentinel-2 L2A: {s2_info['title']}\")\nprint(f\"Description: {s2_info['description'][:100]}...\")\n```\n\n\n### Example 2: Geographic Search\n\n```python\n# Search around San Francisco Bay Area\nbbox = [-122.5, 37.5, -122.0, 38.0]\n\nresults = pc.search(\n    collections=['sentinel-2-l2a'],\n    bbox=bbox,\n    datetime='2024-06-01/2024-08-31',\n    query={'eo:cloud_cover': {'lt': 20}},  # Less than 20% clouds\n    limit=20\n)\n\nitems = results.get_all_items()\nprint(f\"Found {len(items)} items with <20% cloud cover\")\n\n# Convert to DataFrame for analysis\ndf = items.to_dataframe()\nprint(f\"Date range: {df['datetime'].min()} to {df['datetime'].max()}\")\nprint(f\"Cloud cover range: {df['eo:cloud_cover'].min():.1f}% to {df['eo:cloud_cover'].max():.1f}%\")\n```\n\n\n### Example 3: Multi-Provider Comparison\n\n```python\n# Compare results from both providers\nbbox = [-122.2, 47.6, -122.1, 47.7]  # Seattle area\n\npc_results = pc.search(\n    collections=['sentinel-2-l2a'],\n    bbox=bbox,\n    datetime='2024-01-01/2024-03-31'\n)\n\nes_results = es.search(\n    collections=['sentinel-2-l2a'], \n    bbox=bbox,\n    datetime='2024-01-01T00:00:00Z/2024-03-31T23:59:59Z'\n)\n\npc_items = pc_results.get_all_items()\nes_items = es_results.get_all_items()\n\nprint(f\"Planetary Computer: {len(pc_items)} items\")\nprint(f\"EarthSearch: {len(es_items)} items\")\n\n# Compare asset availability\nif pc_items and es_items:\n    pc_assets = pc_items[0].list_assets()\n    es_assets = es_items[0].list_assets()\n    \n    print(f\"PC assets: {pc_assets[:5]}\")\n    print(f\"ES assets: {es_assets[:5]}\")\n```\n\n\n### Example 4: URL Export for External Processing\n\n```python\n# Get URLs for specific bands across multiple items\nitems = pc_results.get_all_items()\n\n# Export RGB band URLs\nrgb_urls = items.get_all_urls(['B04', 'B03', 'B02'])  # Red, Green, Blue\n\n# Save to JSON for external processing\nitems.export_urls_json('sentinel2_rgb_urls.json', ['B04', 'B03', 'B02'])\n\n# Use the URLs with any package\nfirst_item_urls = rgb_urls[list(rgb_urls.keys())[0]]\nprint(f\"Red band URL: {first_item_urls['B04']}\")\n\n# Example with different raster packages\nimport rioxarray\nimport rasterio\nfrom osgeo import gdal\n\nred_url = first_item_urls['B04']\n\n# Option 1: rioxarray\nred_data_xr = rioxarray.open_rasterio(red_url)\n\n# Option 2: rasterio\nwith rasterio.open(red_url) as src:\n    red_data_rio = src.read(1)\n\n# Option 3: GDAL\nred_ds = gdal.Open(red_url)\nred_data_gdal = red_ds.ReadAsArray()\n\nprint(f\"Data shapes - XR: {red_data_xr.shape}, RIO: {red_data_rio.shape}, GDAL: {red_data_gdal.shape}\")\n```\n\n\n### Example 5: Batch Processing Setup\n\n```python\n# Setup for batch processing\nimport json\n\n# Search for monthly data\nresults = pc.search(\n    collections=['sentinel-2-l2a'],\n    bbox=[-120.0, 35.0, -119.0, 36.0],\n    datetime='2024-01-01/2024-12-31',\n    query={'eo:cloud_cover': {'lt': 15}},\n    limit=100\n)\n\nitems = results.get_all_items()\nprint(f\"Found {len(items)} low-cloud scenes\")\n\n# Group by month\ndf = items.to_dataframe()\ndf['month'] = df['datetime'].str[:7]  # YYYY-MM\nmonthly_counts = df.groupby('month').size()\nprint(\"Monthly data availability:\")\nprint(monthly_counts)\n\n# Export all URLs for batch processing\nall_urls = items.get_all_urls(['B04', 'B03', 'B02', 'B08'])  # RGB + NIR\n\n# Save configuration for external processing\nconfig = {\n    'search_params': {\n        'bbox': [-120.0, 35.0, -119.0, 36.0],\n        'datetime': '2024-01-01/2024-12-31',\n        'collections': ['sentinel-2-l2a']\n    },\n    'items_found': len(items),\n    'urls': all_urls\n}\n\nwith open('batch_processing_config.json', 'w') as f:\n    json.dump(config, f, indent=2)\n\nprint(\"Batch processing configuration saved!\")\n```\n\n\n### Example 6: EarthSearch Specific Features\n\n```python\n# EarthSearch uses different asset names\nes = ogapi.earth_search()\n\nes_results = es.search(\n    collections=['sentinel-2-l2a'],\n    bbox=[-122.5, 47.5, -122.0, 48.0],\n    datetime='2024-06-01T00:00:00Z/2024-08-31T23:59:59Z',\n    limit=5\n)\n\nes_items = es_results.get_all_items()\nitem = es_items[0]\n\n# EarthSearch asset names\nitem.print_assets_info()\n\n# Get URLs using EarthSearch naming\nrgb_urls = item.get_band_urls(['red', 'green', 'blue'])\nnir_url = item.get_asset_url('nir')\n\nprint(f\"RGB URLs: {list(rgb_urls.keys())}\")\nprint(f\"NIR URL ready: {nir_url[:50]}...\")\n\n# All URLs (no signing needed for EarthSearch)\nall_urls = item.get_all_asset_urls()\nprint(f\"Total assets available: {len(all_urls)}\")\n```\n\n\n## Best Practices\n\n### 1. Client Configuration\n\n```python\n# Recommended setup\nimport open_geodata_api as ogapi\n\n# Auto-sign PC URLs for immediate use\npc = ogapi.planetary_computer(auto_sign=True)\nes = ogapi.earth_search()\n\n# Or get both at once\nclients = ogapi.get_clients(pc_auto_sign=True)\n```\n\n\n### 2. Search Strategy\n\n```python\n# Start with broad search, then refine\nresults = pc.search(\n    collections=['sentinel-2-l2a'],\n    bbox=your_bbox,\n    datetime='2024-01-01/2024-12-31',\n    query={'eo:cloud_cover': {'lt': 50}},  # Start broad\n    limit=100\n)\n\n# Filter further based on your needs\ndf = results.get_all_items().to_dataframe()\nfiltered_df = df[df['eo:cloud_cover'] < 20]  # Refine cloud cover\n```\n\n\n### 3. URL Management\n\n```python\n# Let the package handle URL signing automatically\nitem = items[0]\n\n# This automatically handles signing based on provider\nblue_url = item.get_asset_url('B02')  # PC: signed, ES: validated\n\n# Override if needed\nunsigned_url = item.get_asset_url('B02', signed=False)\n```\n\n\n### 4. Asset Name Handling\n\n```python\n# Handle different naming conventions gracefully\ndef get_rgb_urls(item):\n    \"\"\"Get RGB URLs regardless of provider naming.\"\"\"\n    assets = item.list_assets()\n    \n    # Try Planetary Computer naming\n    if all(band in assets for band in ['B04', 'B03', 'B02']):\n        return item.get_band_urls(['B04', 'B03', 'B02'])\n    \n    # Try EarthSearch naming  \n    elif all(band in assets for band in ['red', 'green', 'blue']):\n        return item.get_band_urls(['red', 'green', 'blue'])\n    \n    else:\n        print(f\"Available assets: {assets}\")\n        return {}\n\n# Use the function\nrgb_urls = get_rgb_urls(item)\n```\n\n\n### 5. Error Handling\n\n```python\n# Robust search with error handling\ndef safe_search(client, **kwargs):\n    \"\"\"Search with comprehensive error handling.\"\"\"\n    try:\n        results = client.search(**kwargs)\n        items = results.get_all_items()\n        \n        if len(items) == 0:\n            print(\"No items found. Try adjusting search parameters.\")\n            return None\n            \n        print(f\"Found {len(items)} items\")\n        return items\n        \n    except Exception as e:\n        print(f\"Search failed: {e}\")\n        return None\n\n# Use robust search\nitems = safe_search(\n    pc,\n    collections=['sentinel-2-l2a'],\n    bbox=your_bbox,\n    datetime='2024-01-01/2024-03-31'\n)\n```\n\n\n### 6. Memory Management\n\n```python\n# For large datasets, process in batches\ndef process_in_batches(items, batch_size=10):\n    \"\"\"Process items in batches to manage memory.\"\"\"\n    for i in range(0, len(items), batch_size):\n        batch = items[i:i+batch_size]\n        \n        # Get URLs for this batch\n        batch_urls = {}\n        for item in batch:\n            try:\n                batch_urls[item.id] = item.get_band_urls(['B04', 'B03', 'B02'])\n            except Exception as e:\n                print(f\"Failed to get URLs for {item.id}: {e}\")\n        \n        # Process batch_urls as needed\n        yield batch_urls\n\n# Use batch processing\nfor batch_urls in process_in_batches(items):\n    print(f\"Processing batch with {len(batch_urls)} items\")\n    # Your processing logic here\n```\n\n\n## Troubleshooting\n\n### Common Issues and Solutions\n\n#### Issue: \"planetary-computer package not found\"\n\n**Problem:** PC URL signing fails\n\n```python\n# Error: planetary-computer package not found, returning unsigned URL\n```\n\n**Solution:**\n\n```bash\npip install planetary-computer\n```\n\n\n#### Issue: No items found\n\n**Problem:** Search returns empty results\n\n**Solutions:**\n\n```python\n# 1. Check collection names\navailable_collections = pc.list_collections()\nprint(\"Available collections:\", available_collections)\n\n# 2. Expand search area\nbbox = [-123.0, 47.0, -121.0, 48.0]  # Larger area\n\n# 3. Expand date range\ndatetime = '2023-01-01/2024-12-31'  # Larger time window\n\n# 4. Relax cloud cover\nquery = {'eo:cloud_cover': {'lt': 80}}  # More permissive\n```\n\n\n#### Issue: Asset not found\n\n**Problem:** `KeyError: Asset 'B02' not found`\n\n**Solutions:**\n\n```python\n# 1. Check available assets\nitem.print_assets_info()\n\n# 2. Use correct naming for provider\n# PC: B01, B02, B03...\n# ES: coastal, blue, green...\n\n# 3. Handle gracefully\ntry:\n    url = item.get_asset_url('B02')\nexcept KeyError:\n    # Try alternative naming\n    url = item.get_asset_url('blue')\n```\n\n\n#### Issue: EarthSearch datetime format\n\n**Problem:** EarthSearch requires RFC3339 format\n\n**Solution:**\n\n```python\n# Use proper format for EarthSearch\ndatetime_es = '2024-01-01T00:00:00Z/2024-03-31T23:59:59Z'\n\n# Package handles this automatically in most cases\n```\n\n\n#### Issue: Large data downloads\n\n**Problem:** Memory issues with large datasets\n\n**Solutions:**\n\n```python\n# 1. Use overview levels (if your raster package supports it)\nimport rioxarray\ndata = rioxarray.open_rasterio(url, overview_level=2)\n\n# 2. Use chunking\ndata = rioxarray.open_rasterio(url, chunks={'x': 512, 'y': 512})\n\n# 3. Read windows\nimport rasterio\nwith rasterio.open(url) as src:\n    window = rasterio.windows.Window(0, 0, 1024, 1024)\n    data = src.read(1, window=window)\n```\n\n\n### Debug Mode\n\n```python\n# Enable debug information\nimport logging\nlogging.basicConfig(level=logging.DEBUG)\n\n# Check what URLs are being generated\nitem = items[0]\nprint(f\"Item ID: {item.id}\")\nprint(f\"Provider: {item.provider}\")\n\nall_urls = item.get_all_asset_urls()\nfor asset, url in all_urls.items():\n    print(f\"{asset}: {url[:50]}...\")\n```\n\n\n### Validation Steps\n\n```python\n# Validate your setup\ndef validate_setup():\n    \"\"\"Validate package installation and API access.\"\"\"\n    try:\n        import open_geodata_api as ogapi\n        print(\"\u2705 Package imported successfully\")\n        \n        # Test client creation\n        pc = ogapi.planetary_computer()\n        es = ogapi.earth_search()\n        print(\"\u2705 Clients created successfully\")\n        \n        # Test collection listing\n        pc_collections = pc.list_collections()\n        print(f\"\u2705 PC collections: {len(pc_collections)} available\")\n        \n        # Test simple search\n        test_results = pc.search(\n            collections=['sentinel-2-l2a'],\n            bbox=[-122.0, 47.0, -121.0, 48.0],\n            limit=1\n        )\n        test_items = test_results.get_all_items()\n        print(f\"\u2705 Test search: {len(test_items)} items found\")\n        \n        return True\n        \n    except Exception as e:\n        print(f\"\u274c Validation failed: {e}\")\n        return False\n\n# Run validation\nvalidate_setup()\n```\n\n\n## Advanced Usage\n\n### Custom Processing Workflows\n\n```python\n# Example: Multi-temporal analysis setup\ndef setup_temporal_analysis(bbox, date_ranges, max_cloud_cover=20):\n    \"\"\"Setup data for temporal analysis.\"\"\"\n    \n    all_data = {}\n    \n    for period_name, date_range in date_ranges.items():\n        print(f\"Searching for {period_name}...\")\n        \n        results = pc.search(\n            collections=['sentinel-2-l2a'],\n            bbox=bbox,\n            datetime=date_range,\n            query={'eo:cloud_cover': {'lt': max_cloud_cover}},\n            limit=50\n        )\n        \n        items = results.get_all_items()\n        urls = items.get_all_urls(['B04', 'B03', 'B02', 'B08'])  # RGB + NIR\n        \n        all_data[period_name] = {\n            'count': len(items),\n            'date_range': date_range,\n            'urls': urls\n        }\n        \n        print(f\"  Found {len(items)} items\")\n    \n    return all_data\n\n# Use for seasonal analysis\nseasonal_data = setup_temporal_analysis(\n    bbox=[-120.0, 35.0, -119.0, 36.0],\n    date_ranges={\n        'spring_2024': '2024-03-01/2024-05-31',\n        'summer_2024': '2024-06-01/2024-08-31',\n        'fall_2024': '2024-09-01/2024-11-30'\n    }\n)\n```\n\n\n### Integration with Other Libraries\n##### Install Required Packages\n```python\npip install stackstac pystac\n```\n##### The Custom Functions\n```python\n# Example: Integration with STAC-tools\ndef integrate_with_stac_tools(items):\n    \"\"\"Convert to format compatible with other STAC tools.\"\"\"\n    \n    # Export as standard STAC format\n    stac_collection = items.to_dict()  # GeoJSON FeatureCollection\n    \n    # Use with pystac\n    try:\n        import pystac\n        \n        # Convert items for pystac\n        pystac_items = []\n        for item_data in items.to_list():\n            pystac_item = pystac.Item.from_dict(item_data)\n            pystac_items.append(pystac_item)\n        \n        print(f\"Converted {len(pystac_items)} items to pystac format\")\n        return pystac_items\n        \n    except ImportError:\n        print(\"pystac not available\")\n        return stac_collection\n\n# Example: Integration with stackstac\ndef prepare_for_stackstac(items, bands=['B04', 'B03', 'B02']):\n    \"\"\"Prepare data for stackstac processing.\"\"\"\n    \n    try:\n        import stackstac\n        \n        # Get STAC items in proper format\n        stac_items = [item.to_dict() for item in items]\n        \n        # Note: URLs need to be properly signed\n        # The package handles this automatically\n        \n        print(f\"Prepared {len(stac_items)} items for stackstac\")\n        print(f\"Bands: {bands}\")\n        \n        return stac_items\n        \n    except ImportError:\n        print(\"stackstac not available\")\n        return None\n\nif __name__ == \"__main__\":\n    # Use the functions\n    stac_items = integrate_with_stac_tools(items)\n    stackstac_items = prepare_for_stackstac(items)\n    print(f\"STAC items: {stac_items} \\nStackSTAC items: {stackstac_items}\")\n    print(f\"STAC items: {len(stac_items)} \\nStackSTAC items: {len(stackstac_items)}\")\n    print(\"Integration and preparation complete!\")\n```\n\n\n### Custom URL Processing\n\n```python\n# Example: Custom URL validation and processing\ndef process_urls_custom(items, custom_processor=None):\n    \"\"\"Process URLs with custom logic.\"\"\"\n    \n    def default_processor(url):\n        \"\"\"Default URL processor.\"\"\"\n        # Add custom headers, caching, etc.\n        return url\n    \n    processor = custom_processor or default_processor\n    \n    processed_urls = {}\n    \n    for item in items:\n        item_urls = item.get_all_asset_urls()\n        processed_item_urls = {}\n        \n        for asset, url in item_urls.items():\n            processed_url = processor(url)\n            processed_item_urls[asset] = processed_url\n        \n        processed_urls[item.id] = processed_item_urls\n    \n    return processed_urls\n\n# Example custom processor\ndef add_caching_headers(url):\n    \"\"\"Add caching parameters to URL.\"\"\"\n    if '?' in url:\n        return f\"{url}&cache=3600\"\n    else:\n        return f\"{url}?cache=3600\"\n\n# Use custom processing\ncached_urls = process_urls_custom(items, add_caching_headers)\nprint(f\"Cached URLs: {cached_urls}\")\n```\n## Utils Functions\n\n### Utils Functions - Usage Examples\n\n```python\nimport open_geodata_api as ogapi\nfrom open_geodata_api.utils import (\n    filter_by_cloud_cover,\n    download_datasets,\n    download_url,\n    download_from_json,\n    download_seasonal,\n    download_single_file,\n    download_url_dict,\n    download_items,\n    download_seasonal_data,\n    create_download_summary,\n    is_url_expired,\n    is_signed_url,\n    re_sign_url_if_needed\n)\n\n# Setup clients\npc = ogapi.planetary_computer(auto_sign=True)\nes = ogapi.earth_search()\n```\n\n#### Example 1: Complete Workflow - Search and Filter by Cloud Cover\n```python\nprint(\"\ud83d\udd0d Searching for Sentinel-2 data...\")\nresults = pc.search(\n    collections=[\"sentinel-2-l2a\"],\n    bbox=[-122.5, 47.5, -122.0, 48.0],  # Seattle area\n    datetime=\"2024-06-01/2024-08-31\",\n    limit=20\n)\n\nitems = results.get_all_items()\nprint(f\"Found {len(items)} items\")\n\n# Filter by cloud cover using utils\nclear_items = filter_by_cloud_cover(items, max_cloud_cover=15)\nprint(f\"After filtering: {len(clear_items)} clear items (<15% clouds)\")\n```\n\n#### Example 2: Download Single Asset from Search Results\n```python\nprint(\"\\n\ud83d\udce5 Downloading single asset...\")\nfirst_item = clear_items[^0]\nfirst_item.print_assets_info()\n\n# Get a single band URL and download\nred_url = first_item.get_asset_url('B04')  # Red band, auto-signed\ndownloaded_file = download_single_file(\n    red_url, \n    destination=\"./data/red_band.tif\",\n    provider=\"planetary_computer\"\n)\nprint(f\"Downloaded: {downloaded_file}\")\n```\n\n#### Example 3: Download RGB Bands from Multiple Items\n```python\nprint(\"\\n\ud83c\udfa8 Downloading RGB bands from multiple items...\")\nrgb_downloads = download_items(\n    clear_items[:3],  # First 3 clear items\n    base_destination=\"./rgb_data/\",\n    asset_keys=['B04', 'B03', 'B02'],  # Red, Green, Blue\n    create_product_folders=True\n)\n\nprint(f\"Downloaded RGB data for {len(rgb_downloads)} items\")\n```\n\n#### Example 4: Multi-Provider Data Collection and Download\n```python\nprint(\"\\n\ud83c\udf0d Comparing data from multiple providers...\")\n\n# Search both providers\nsearch_params = {\n    'collections': ['sentinel-2-l2a'],\n    'bbox': [-120.0, 35.0, -119.0, 36.0],  # California\n    'datetime': '2024-07-01/2024-07-31',\n    'limit': 5\n}\n\npc_results = pc.search(**search_params)\nes_results = es.search(**search_params)\n\npc_items = pc_results.get_all_items()\nes_items = es_results.get_all_items()\n\nprint(f\"PC found: {len(pc_items)} items\")\nprint(f\"ES found: {len(es_items)} items\")\n\n# Filter both collections\npc_clear = filter_by_cloud_cover(pc_items, max_cloud_cover=20)\nes_clear = filter_by_cloud_cover(es_items, max_cloud_cover=20)\n\n# Download from both providers\nprint(\"\ud83d\udce6 Downloading from Planetary Computer...\")\npc_downloads = download_items(\n    pc_clear[:2], \n    base_destination=\"./pc_data/\",\n    asset_keys=['B08', 'B04'],  # NIR, Red for NDVI\n)\n\nprint(\"\ud83d\udce6 Downloading from EarthSearch...\")\nes_downloads = download_items(\n    es_clear[:2], \n    base_destination=\"./es_data/\",\n    asset_keys=['nir', 'red'],  # ES naming convention\n)\n```\n\n#### Example 5: Seasonal Analysis Workflow\n```python\nprint(\"\\n\ud83c\udf31 Setting up seasonal analysis...\")\n\ndef collect_seasonal_data(bbox, year):\n    \"\"\"Collect data for seasonal analysis.\"\"\"\n    seasons = {\n        'spring': f'{year}-03-01/{year}-05-31',\n        'summer': f'{year}-06-01/{year}-08-31', \n        'fall': f'{year}-09-01/{year}-11-30',\n        'winter': f'{year}-12-01/{year+1}-02-28'\n    }\n    \n    seasonal_data = {}\n    \n    for season, date_range in seasons.items():\n        print(f\"\ud83d\udd0d Searching {season} {year} data...\")\n        \n        results = pc.search(\n            collections=['sentinel-2-l2a'],\n            bbox=bbox,\n            datetime=date_range,\n            query={'eo:cloud_cover': {'lt': 25}},\n            limit=10\n        )\n        \n        items = results.get_all_items()\n        filtered_items = filter_by_cloud_cover(items, max_cloud_cover=20)\n        \n        # Get URLs for NDVI calculation\n        urls = filtered_items.get_all_urls(['B08', 'B04'])  # NIR, Red\n        \n        seasonal_data[season] = {\n            'count': len(filtered_items),\n            'date_range': date_range,\n            'urls': urls\n        }\n        \n        print(f\"  Found {len(filtered_items)} clear scenes\")\n    \n    return seasonal_data\n\n# Collect seasonal data\nbbox = [-121.0, 38.0, -120.5, 38.5]  # Northern California\nseasonal_data = collect_seasonal_data(bbox, 2024)\n\n# Download seasonal data using utils\nseasonal_downloads = download_seasonal_data(\n    seasonal_data,\n    base_destination=\"./seasonal_analysis/\",\n    seasons=['spring', 'summer'],  # Only spring and summer\n    asset_keys=['B08', 'B04']  # NIR and Red bands\n)\n```\n\n#### Example 6: URL Management and Re-signing\n```python\nprint(\"\\n\ud83d\udd10 URL management example...\")\n\n# Get some URLs from items\nitem = pc_items[^0] if pc_items else clear_items[^0]\nall_urls = item.get_all_asset_urls()\n\n# Check URL status\nfor asset, url in list(all_urls.items())[:3]:\n    print(f\"\\n\ud83d\udd17 Asset: {asset}\")\n    print(f\"   Signed: {is_signed_url(url)}\")\n    print(f\"   Expired: {is_url_expired(url)}\")\n    \n    # Re-sign if needed\n    fresh_url = re_sign_url_if_needed(url, provider=\"planetary_computer\")\n    if fresh_url != url:\n        print(f\"   \u2705 URL was re-signed\")\n```\n\n#### Example 7: Batch Processing with URL Dictionary\n```python\nprint(\"\\n\ud83d\udcca Batch processing workflow...\")\n\n# Create a custom URL dictionary from search results\ncustom_urls = {}\nfor i, item in enumerate(clear_items[:3]):\n    item_id = f\"sentinel2_{item.id[-8:]}\"  # Shortened ID\n    # Get specific bands for analysis\n    item_urls = item.get_band_urls(['B02', 'B03', 'B04', 'B08'])\n    custom_urls[item_id] = item_urls\n\nprint(f\"Created custom URL dictionary with {len(custom_urls)} items\")\n\n# Download using URL dictionary\nbatch_downloads = download_url_dict(\n    {k: v for k, v in list(custom_urls.items())[^0].items()},  # First item only\n    base_destination=\"./batch_data/\",\n    provider=\"planetary_computer\",\n    create_subfolders=True\n)\n```\n#### Example 8: Export and Import Workflow\n```python\nprint(\"\\n\ud83d\udcbe Export/Import workflow...\")\n\n# Export URLs to JSON for later processing\nimport json\nwith open('./data_urls.json', 'w') as f:\n    json.dump(custom_urls, f, indent=2)\n\nprint(\"\ud83d\udce4 URLs exported to data_urls.json\")\n\n# Download from JSON file using utils\njson_downloads = download_from_json(\n    './data_urls.json',\n    destination=\"./from_json/\",\n    asset_keys=['B04', 'B08'],  # Only specific bands\n    create_folders=True\n)\n```\n\n#### Example 9: Download Summary and Reporting\n```python\nprint(\"\\n\ud83d\udccb Creating download summary...\")\n\n# Combine all download results\nall_downloads = {\n    'rgb_downloads': rgb_downloads,\n    'pc_downloads': pc_downloads,\n    'es_downloads': es_downloads,\n    'seasonal_downloads': seasonal_downloads,\n    'batch_downloads': batch_downloads,\n    'json_downloads': json_downloads\n}\n\n# Create comprehensive summary\nsummary = create_download_summary(\n    all_downloads, \n    output_file=\"./download_report.json\"\n)\n\nprint(f\"\ud83d\udcca Download Summary:\")\nprint(f\"   Total files: {summary['total_files']}\")\nprint(f\"   Successful: {summary['successful_downloads']}\")\nprint(f\"   Failed: {summary['failed_downloads']}\")\nprint(f\"   Success rate: {summary['success_rate']}\")\n```\n\n#### Example 10: Advanced Filtering and Processing\n```python\nprint(\"\\n\ud83d\udd2c Advanced processing workflow...\")\n\n# Multi-step filtering\ndef advanced_processing_workflow(bbox, max_cloud=10):\n    \"\"\"Advanced workflow with multiple filtering steps.\"\"\"\n    \n    # Step 1: Search with broader criteria\n    results = pc.search(\n        collections=['sentinel-2-l2a'],\n        bbox=bbox,\n        datetime='2024-06-01/2024-09-30',\n        limit=50\n    )\n    \n    items = results.get_all_items()\n    print(f\"Step 1: Found {len(items)} total items\")\n    \n    # Step 2: Filter by cloud cover\n    clear_items = filter_by_cloud_cover(items, max_cloud_cover=max_cloud)\n    print(f\"Step 2: {len(clear_items)} items with <{max_cloud}% clouds\")\n    \n    # Step 3: Convert to DataFrame for advanced filtering\n    df = clear_items.to_dataframe(include_geometry=False)\n    \n    # Step 4: Filter by date (summer months only)\n    summer_mask = df['datetime'].str.contains('2024-0[^678]')  # June, July, August\n    summer_items_ids = df[summer_mask]['id'].tolist()\n    \n    # Step 5: Get items for summer period\n    summer_items = [item for item in clear_items if item.id in summer_items_ids]\n    print(f\"Step 3: {len(summer_items)} summer items\")\n    \n    # Step 6: Download analysis-ready data\n    analysis_downloads = download_items(\n        summer_items[:5],  # Top 5 summer items\n        base_destination=\"./analysis_ready/\",\n        asset_keys=['B02', 'B03', 'B04', 'B08', 'B11', 'B12'],  # Multi-spectral\n        create_product_folders=True\n    )\n    \n    return analysis_downloads, summer_items\n\n# Run advanced workflow\nanalysis_results, summer_items = advanced_processing_workflow(\n    bbox=[-122.0, 37.0, -121.5, 37.5],  # San Francisco Bay\n    max_cloud=5\n)\n\nprint(f\"\u2705 Analysis-ready data downloaded for {len(analysis_results)} items\")\n```\n\n#### Example 11: Error Handling and Resilient Downloads\n```python\nprint(\"\\n\ud83d\udee1\ufe0f Resilient download example...\")\n\ndef resilient_download(items, max_retries=3):\n    \"\"\"Download with retry logic and error handling.\"\"\"\n    \n    successful_downloads = {}\n    failed_downloads = {}\n    \n    for item in items[:2]:  # Process first 2 items\n        item_id = item.id\n        retries = 0\n        \n        while retries < max_retries:\n            try:\n                # Try to download key bands\n                downloads = download_items(\n                    [item],\n                    base_destination=f\"./resilient_data/attempt_{retries+1}/\",\n                    asset_keys=['B04', 'B08'],\n                    create_product_folders=True\n                )\n                \n                successful_downloads[item_id] = downloads\n                print(f\"\u2705 Successfully downloaded {item_id}\")\n                break\n                \n            except Exception as e:\n                retries += 1\n                print(f\"\u274c Attempt {retries} failed for {item_id}: {e}\")\n                \n                if retries >= max_retries:\n                    failed_downloads[item_id] = str(e)\n                    print(f\"\ud83d\udc80 Gave up on {item_id} after {max_retries} attempts\")\n    \n    return successful_downloads, failed_downloads\n\n# Run resilient download\nsuccessful, failed = resilient_download(clear_items)\nprint(f\"Resilient download completed: {len(successful)} successful, {len(failed)} failed\")\n\nprint(\"\\n\ud83c\udf89 All utils function examples completed!\")\nprint(f\"Check your './data/' directory for downloaded files\")\n```\n\n## CLI Usage\n\n### Command Line Interface (CLI) Usage\nOpen Geodata API provides a comprehensive CLI for satellite data discovery, filtering, and downloading. After installation, use the `ogapi` command to access all functionality.\n\n#### Show package information\n```bash\nogapi info\n```\n\n#### Get help for any command\n```bash\nogapi --help\n```\n```bash\nogapi collections --help\n```\n```bash\nogapi search items --help\n```\n### Collections Management\n#### List all collections from both providers\n```bash\nogapi collections list\n```\n#### List from specific provider\n```bash\nogapi collections list --provider pc\n```\n```bash\nogapi collections list --provider es\n```\n\n#### Filter collections by keyword\n```bash\nogapi collections list --filter sentinel\n```\n\n#### Save results to file\n```bash\nogapi collections list --output collections.json\n```\n\n### Search Collections\n#### Find collections by keyword\n```bash\nogapi collections search sentinel\n```\n```bash\nogapi collections search landsat --provider pc\n```\n```bash\nogapi collections search modis --provider both\n```\n\n### Get Collection Information\n#### Get detailed collection info\n```bash\nogapi collections info sentinel-2-l2a\n```\n```bash\nogapi collections info sentinel-2-l2a --provider es\n```\n```bash\nogapi collections info landsat-c2-l2 --output collection_info.json\n```\n\n### Data Search\n#### Search for Sentinel-2 data in Seattle area\n```bash\nogapi search items --collections sentinel-2-l2a --bbox \"-122.5,47.5,-122.0,48.0\" --datetime \"2024-06-01/2024-08-31\" --limit 10\n```\n\n#### Search with cloud cover filter\n```bash\nogapi search items --collections sentinel-2-l2a --bbox \"-122.5,47.5,-122.0,48.0\" --cloud-cover 20 --output search_results.json\n```\n\n#### Search multiple collections\n```bash\nogapi search items --collections \"sentinel-2-l2a,landsat-c2-l2\" --bbox \"-120.0,35.0,-119.0,36.0\" --datetime \"2024-01-01/2024-12-31\"\n```\n#### Advanced Search with JSON Query\n```bash\nogapi search items --collections sentinel-2-l2a --bbox \"-122.5,47.5,-122.0,48.0\" --query '{\"eo:cloud_cover\":{\"lt\":15},\"platform\":{\"eq\":\"sentinel-2a\"}}' --output advanced_search.json\n```\n\n#### Find recent clear data (last 30 days)\n```bash\nogapi search quick sentinel-2-l2a \"-122.5,47.5,-122.0,48.0\"\n```\n\n#### Customize time range and cloud threshold\n```bash\nogapi search quick sentinel-2-l2a \"-122.5,47.5,-122.0,48.0\" --days 7 --cloud-cover 10 --limit 5\n```\n\n#### Save results for later processing\n```bash\nogapi search quick landsat-c2-l2 \"-120.0,35.0,-119.0,36.0\" --output recent_landsat.json\n```\n\n### Compare Data Availability Between Providers\n#### Compare Sentinel-2 availability\n```bash\nogapi search compare --collections sentinel-2-l2a --bbox \"-122.5,47.5,-122.0,48.0\" --datetime \"2024-06-01/2024-08-31\"\n```\n#### Compare with cloud filtering\n```bash\nogapi search compare --collections sentinel-2-l2a --bbox \"-122.5,47.5,-122.0,48.0\" --cloud-cover 25 --output comparison_results.json\n```\n\n### Working with Items\n#### Show info for first item in search results\n```bash\nogapi items info search_results.json\n```\n#### Show info for specific item by index\n```bash\nogapi items info search_results.json --item-index 2\n```\n#### Show detailed metadata\n```bash\nogapi items info search_results.json --show-all --output item_details.json\n```\n#### List all assets for an item\n```bash\nogapi items assets search_results.json\n```\n#### Filter assets by pattern\n```bash\nogapi items assets search_results.json --type \"image/tiff\"\n```\n#### Show URLs for assets\n```bash\nogapi items assets search_results.json --show-urls\n```\n#### Get URLs for RGB bands\n```bash\nogapi items urls search_results.json --assets \"B04,B03,B02\"\n```\n#### Get all asset URLs\n```bash\nogapi items urls search_results.json --output all_urls.json\n```\n#### Get URLs by pattern\n```bash\nogapi items urls search_results.json --pattern \"B0\" --output optical_bands.json\n```\n#### Get unsigned URLs\n```bash\nogapi items urls search_results.json --unsigned\n```\n### Compare Items by Quality\n#### Compare items by cloud cover (find clearest)\n```bash\nogapi items compare search_results.json\n```\n#### Compare by date (find most recent)\n```bash\nogapi items compare search_results.json --metric date\n```\n#### Compare asset availability\n```bash\nogapi items compare search_results.json --metric assets --max-items 10\n```\n### Data Download\n#### Download all assets from search results\n```bash\nogapi download search-results search_results.json\n```\n#### Download specific bands only\n```bash\nogapi download search-results search_results.json --assets \"B04,B03,B02\" --destination \"./rgb_data/\"\n```\n#### Download with additional filtering\n```bash\nogapi download search-results search_results.json --cloud-cover 15 --max-items 5 --assets \"B08,B04\"\n```\n#### Create flat file structure\n```bash\nogapi download search-results search_results.json --flat-structure --destination \"./satellite_data/\n```\n#### Download single file\n```bash\nogapi download url \"https://example.com/sentinel2_B04.tif\"\n```\n#### Download to specific location\n```bash\nogapi download url \"https://example.com/B04.tif\" --destination \"./data/red_band.tif\"\n```\n#### Download with provider specification (it will handdle url validation)\n```bash\nogapi download url \"https://pc.example.com/B04.tif\" --provider pc\n```\n#### Download from exported URLs\n```bash\nogapi download urls-json exported_urls.json\n```\n#### Custom destination\n```bash\nogapi download urls-json urls.json --destination \"./downloads/\" --flat-structure\n```\n#### Download all seasons from seasonal JSON\n```bash\nogapi download seasonal seasonal_data.json\n```\n#### Download specific seasons and assets\n```bash\nogapi download seasonal seasonal_data.json --seasons \"spring,summer\" --assets \"B08,B04\" --destination \"./time_series/\"\n```\n### Filter by Cloud Cover\n#### Filter search results by cloud cover\n```bash\nogapi utils filter-clouds search_results.json --max-cloud-cover 20\n```\n#### Filter and save results\n```bash\nogapi utils filter-clouds search_results.json --max-cloud-cover 15 --output clear_results.json --show-stats\n```\n### Export URLs\n#### Export all URLs from search results\n```bash\nogapi utils export-urls search_results.json --output all_urls.json\n```\n#### Export specific assets\n```bash\nogapi utils export-urls search_results.json --output rgb_urls.json --assets \"B04,B03,B02\"\n```\n#### Export in simple format\n```bash\nogapi utils export-urls search_results.json --output simple_urls.json --format simple\n```\n#### Export unsigned URLs\n```bash\nogapi utils export-urls search_results.json --output unsigned_urls.json --unsigned\n```\n### Validate URLs\n#### Basic URL validation\n```bash\nogapi utils validate-urls urls.json\n```\n#### Check accessibility (HTTP requests)\n```bash\nogapi utils validate-urls urls.json --check-access\n```\n#### Fix expired URLs\n```bash\nogapi utils validate-urls urls.json --fix-expired --output fixed_urls.json\n```\n#### Skip expiry check for speed\n```bash\nogapi utils validate-urls urls.json --no-check-expiry\n```\n### Analyze Search Results\n#### Analyze cloud cover distribution\n```bash\nogapi utils analyze search_results.json\n```\n#### Temporal analysis\n```bash\nogapi utils analyze search_results.json --metric temporal\n```\n#### Asset availability analysis\n```bash\nogapi utils analyze search_results.json --metric assets --output analysis_report.json\n```\n### Create Download Summaries\n#### Create detailed download summary\n```bash\nogapi utils download-summary download_results.json\n```\n#### Brief summary\n```bash\nogapi utils download-summary results.json --format brief\n```\n#### Save summary report\n```bash\nogapi utils download-summary results.json --output report.json\n```\n### Complete Workflow Examples\n#### 1.1: Search for data (Basic RGB Download Workflow)\n```bash\nogapi search items --collections sentinel-2-l2a --bbox \"-122.5,47.5,-122.0,48.0\" --datetime \"2024-06-01/2024-08-31\" --cloud-cover 20 --output search_results.json\n```\n#### 1.2: Filter for very clear imagery\n```bash\nogapi utils filter-clouds search_results.json --max-cloud-cover 10 --output clear_results.json\n```\n#### 1.3: Download RGB bands\n```bash\nogapi download search-results clear_results.json --assets \"B04,B03,B02\" --destination \"./rgb_analysis/\"\n```\n#### 1.4: Create summary\n```bash\nogapi utils download-summary download_results.json\n```\n#### 2.1: Search for data (NDVI Analysis Workflow)\n```bash\nogapi search items --collections sentinel-2-l2a --bbox \"-120.0,35.0,-119.0,36.0\" --datetime \"2024-01-01/2024-12-31\" --cloud-cover 25 --output yearly_search.json\n```\n#### 2.2: Filter by seasons and export URLs\n```bash\nogapi utils filter-clouds yearly_search.json --max-cloud-cover 15 --output clear_yearly.json\n```\n```bash\nogapi utils export-urls clear_yearly.json --assets \"B08,B04\" --output ndvi_urls.json\n```\n#### 2.3: Download NDVI bands\n```bash\nogapi download urls-json ndvi_urls.json --destination \"./ndvi_analysis/\"\n```\n#### 3.1 Compare data availability (Multi-Provider Comparison)\n```bash\nogapi search compare --collections sentinel-2-l2a --bbox \"-122.5,47.5,-122.0,48.0\" --datetime \"2024-06-01/2024-08-31\" --output comparison.json\n```\n#### 3.2 Search both providers separately\n```bash\nogapi search items --provider pc --collections sentinel-2-l2a --bbox \"-122.5,47.5,-122.0,48.0\" --cloud-cover 20 --output pc_results.json\n```\n```bash\nogapi search items --provider es --collections sentinel-2-l2a --bbox \"-122.5,47.5,-122.0,48.0\" --cloud-cover 20 --output es_results.json\n```\n#### 3.3 Download from best provider\n```bash\nogapi download search-results pc_results.json --max-items 3 --destination \"./pc_data/\"\n```\n#### 4.1 Search and analyze (Quality Assessment Workflow) \n```bash\nogapi search items --collections sentinel-2-l2a --bbox \"-122.5,47.5,-122.0,48.0\" --limit 50 --output large_search.json\n```\n#### 4.2 Analyze data quality \n```bash\nogapi utils analyze large_search.json --metric cloud_cover\n```\n```bash\nogapi utils analyze large_search.json --metric temporal\n```\n```bash\nogapi utils analyze large_search.json --metric assets\n```\n#### 4.3 Compare individual items\n```bash\nogapi items compare large_search.json --metric cloud_cover\n```\n#### 4.4 Download best items only\n```bash\nogapi utils filter-clouds large_search.json --max-cloud-cover 10 --output best_quality.json\n```\n```bash\nogapi download search-results best_quality.json --max-items 5\n```\n### Global Options\n**All commands support these global options:**\n#### Enable verbose output for debugging\n```bash\nogapi --verbose [command]\n```\n#### Show version\n```bash\nogapi --version\n```\n#### Get help for any command\n```bash\nogapi [command] --help\n```\n```bash\nogapi [command] [subcommand] --help\n```\n\n### Tips and Best Practices\n\n1. **Start Small**: Use `--limit` and `--max-items` to test workflows before large downloads\n2. **Save Results**: Always use `--output` to save search results for reprocessing\n3. **Filter Early**: Use cloud cover filters to reduce data volume\n4. **Check Status**: Use validation commands before large downloads\n5. **Resume Downloads**: Most download commands support resuming interrupted transfers\n6. **Use Verbose Mode**: Add `--verbose` for detailed debugging information\n\n### Environment Variables\n#### Optional: Set default provider\n```bash\nexport OGAPI_DEFAULT_PROVIDER=pc\n```\n#### Optional: Set default destination\n```bash\nexport OGAPI_DEFAULT_DEST=./satellite_data/\n```\n\n## FAQ\n\n### General Questions\n\n**Q: What makes this package different from using APIs directly?**\n\nA: Key advantages:\n\n- Unified interface across multiple APIs\n- Automatic URL signing/validation\n- Consistent error handling\n- No lock-in to specific data reading packages\n- Built-in best practices\n\n**Q: Can I use this with my existing geospatial workflow?**\n\nA: Absolutely! The package provides URLs that work with any raster reading library:\n\n```python\nurl = item.get_asset_url('red')\n\n# Use with your existing tools\nimport rioxarray; data = rioxarray.open_rasterio(url)\nimport rasterio; data = rasterio.open(url)\nfrom osgeo import gdal; data = gdal.Open(url)\n```\n\n**Q: Do I need API keys?**\n\nA: Only for Planetary Computer. EarthSearch is completely open.\n\n### Technical Questions\n\n**Q: How does automatic URL signing work?**\n\nA: When `auto_sign=True`, the package:\n\n1. Detects the provider (PC vs ES)\n2. For PC: Uses the planetary-computer package to sign URLs\n3. For ES: Returns URLs as-is (no signing needed)\n4. You can override with `signed=False/True`\n\n**Q: What about rate limiting?**\n\nA: Both APIs have rate limits:\n\n- **Planetary Computer**: Generous limits for signed URLs\n- **EarthSearch**: Standard HTTP rate limits\n\nThe package doesn't implement rate limiting - use your own if needed.\n\n**Q: Can I cache results?**\n\nA: Yes, several approaches:\n\n```python\n# 1. Export URLs to JSON\nitems.export_urls_json('cache.json')\n\n# 2. Save DataFrames\ndf = items.to_dataframe()\ndf.to_parquet('metadata_cache.parquet')\n\n# 3. Use your own caching layer\n```\n\n**Q: How do I handle different projections?**\n\nA: The package provides URLs - projection handling is up to your raster library:\n\n```python\nimport rioxarray\ndata = rioxarray.open_rasterio(url)\ndata_reprojected = data.rio.reproject('EPSG:4326')\n```\n\n\n### Troubleshooting Questions\n\n**Q: Why am I getting \"Asset not found\" errors?**\n\nA: Different providers use different asset names:\n\n- **PC**: B01, B02, B03, B04...\n- **EarthSearch**: coastal, blue, green, red...\n\nUse `item.print_assets_info()` to see available assets.\n\n**Q: Search returns no results but data should exist**\n\nA: Common issues:\n\n1. **Bbox order**: Use [west, south, east, north]\n2. **Date format**: PC accepts \"YYYY-MM-DD\", ES prefers RFC3339\n3. **Collection names**: Use `client.list_collections()` to verify\n4. **Cloud cover**: Try relaxing the threshold\n\n**Q: URLs work but data loading is slow**\n\nA: Optimization strategies:\n\n1. Use overview levels: `rioxarray.open_rasterio(url, overview_level=2)`\n2. Enable chunking: `rioxarray.open_rasterio(url, chunks=True)`\n3. Read smaller windows with rasterio\n4. Consider geographic proximity to data\n\n### Integration Questions\n\n**Q: Can I use this with Jupyter notebooks?**\n\nA: Yes! The package works great in Jupyter:\n\n```python\n# Display asset info\nitem.print_assets_info()\n\n# Show DataFrames\ndf = items.to_dataframe()\ndisplay(df)\n\n# Plot with matplotlib/cartopy\nimport matplotlib.pyplot as plt\ndata = rioxarray.open_rasterio(url)\ndata.plot()\n```\n\n**Q: How do I integrate with QGIS/ArcGIS?**\n\nA: Export URLs and use them directly:\n\n```python\n# Get URLs\nurls = item.get_all_asset_urls()\n\n# In QGIS: Add Raster Layer -> use the URL directly\n# In ArcGIS: Add Data -> Raster Dataset -> paste URL\n```\n\n**Q: Can I use this in production systems?**\n\nA: Yes! The package is designed for production use:\n\n- Robust error handling\n- No forced dependencies\n- Clean separation of concerns\n- Comprehensive logging support\n\n**Q: How do I contribute or report issues?**\n\nA: Visit the GitHub repository:\n\n- Report issues: GitHub Issues\n- Contribute: Pull Requests welcome\n- Documentation: Help improve this guide\n\n---\n\nThis completes the comprehensive user guide for Open Geodata API. The package provides a clean, flexible foundation for accessing open geospatial data while letting you maintain full control over data processing and analysis workflows.\n\n--- End ---\n\n[^1]: https://developers.arcgis.com/python/latest/guide/tutorials/import-data/\n\n[^2]: https://github.com/geopython/pygeoapi/blob/master/pygeoapi/openapi.py\n\n[^3]: https://opencagedata.com/api\n\n[^4]: https://opencagedata.com/tutorials/geocode-in-python\n\n[^5]: https://guides.library.columbia.edu/geotools/Python\n\n[^6]: https://pygeoapi.io\n\n[^7]: https://live.osgeo.org/en/quickstart/pygeoapi_quickstart.html\n\n[^8]: https://packaging.python.org\n\n[^9]: [http://r-project.ro/conference2018/presentations/Tutorial_Spatial_Analysis_in_R_with_Open_Geodata_-_uRos2018.pdf](https://github.com/Mirjan-Ali-Sha/open-geodata-api/blob/main/Open%20Geodata%20API%20-%20Complete%20User%20Guide.pdf)\n\n[^10]: https://geodata.readthedocs.io\n\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Unified Python client for open geospatial data APIs: Planetary Computer, EarthSearch, and more",
    "version": "0.3.0",
    "project_urls": {
        "Bug Reports": "https://github.com/Mirjan-Ali-Sha/open-geodata-api/issues",
        "Documentation": "https://open-geodata-api.readthedocs.io",
        "Homepage": "https://github.com/Mirjan-Ali-Sha/open-geodata-api",
        "License": "https://github.com/Mirjan-Ali-Sha/open-geodata-api/blob/main/LICENSE",
        "Repository": "https://github.com/Mirjan-Ali-Sha/open-geodata-api",
        "Third-Party Notices": "https://github.com/Mirjan-Ali-Sha/open-geodata-api/blob/main/THIRD_PARTY_NOTICES.md"
    },
    "split_keywords": [
        "api-client",
        " cli",
        " earth-observation",
        " earthsearch",
        " geodata",
        " geospatial",
        " open-data",
        " planetary-computer",
        " remote-sensing",
        " satellite",
        " stac"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "a193c5eefdd139eba1e1614532b643e545474c5e7cdaf2209172c9c2cce2e1e8",
                "md5": "5c36ec099a54a68a18acaf235c6f05d9",
                "sha256": "fc202c1f107172f75b61ac878a7efb4b7d04e029858450bd4c0d76dd25dfd08c"
            },
            "downloads": -1,
            "filename": "open_geodata_api-0.3.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "5c36ec099a54a68a18acaf235c6f05d9",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 97542,
            "upload_time": "2025-07-13T17:58:09",
            "upload_time_iso_8601": "2025-07-13T17:58:09.871339Z",
            "url": "https://files.pythonhosted.org/packages/a1/93/c5eefdd139eba1e1614532b643e545474c5e7cdaf2209172c9c2cce2e1e8/open_geodata_api-0.3.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "884af6e353404bb9fc0cd12fdaf5bb4bc2efaf70c35820a30462f699d7e562eb",
                "md5": "c4df9441b22c4f99ee2457d69790cad8",
                "sha256": "e07305393bbf120122fc820946a417644cfa8a9316483e9ce085312622c4ce97"
            },
            "downloads": -1,
            "filename": "open_geodata_api-0.3.0.tar.gz",
            "has_sig": false,
            "md5_digest": "c4df9441b22c4f99ee2457d69790cad8",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 1343828,
            "upload_time": "2025-07-13T17:58:11",
            "upload_time_iso_8601": "2025-07-13T17:58:11.887924Z",
            "url": "https://files.pythonhosted.org/packages/88/4a/f6e353404bb9fc0cd12fdaf5bb4bc2efaf70c35820a30462f699d7e562eb/open_geodata_api-0.3.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-13 17:58:11",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "Mirjan-Ali-Sha",
    "github_project": "open-geodata-api",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "requirements": [
        {
            "name": "requests",
            "specs": [
                [
                    ">=",
                    "2.25.0"
                ]
            ]
        },
        {
            "name": "pandas",
            "specs": [
                [
                    ">=",
                    "1.3.0"
                ]
            ]
        },
        {
            "name": "sphinx-copybutton",
            "specs": []
        }
    ],
    "lcname": "open-geodata-api"
}
        
Elapsed time: 1.07581s