# Tutorial: ArcGIS Pro Integration

EarthForge runs in the ArcGIS Pro Python environment. Use it to fetch cloud-native data, validate COGs, and convert vector formats — then hand the results off to ArcGIS Pro for further analysis or cartography.

## Why Use EarthForge with ArcGIS Pro?

ArcGIS Pro can read COGs and GeoParquet directly, but it doesn't have tooling for:

- **STAC discovery** — searching cloud catalogs and downloading specific assets
- **COG validation** — verifying a file's internal structure before adding it to a project
- **Format conversion** — converting Shapefiles to GeoParquet with proper geo metadata
- **Predicate pushdown queries** — filtering large GeoParquet files without loading them into memory

EarthForge handles these tasks from within the ArcGIS Pro Python environment.

## Setup

ArcGIS Pro ships with a managed `conda` environment. Install EarthForge into it:

```bash
# Open ArcGIS Pro Python Command Prompt
# (Start → ArcGIS → Python Command Prompt)

pip install earthforge[stac,raster,vector]
```

!!! note
    GDAL and rasterio are already installed in the ArcGIS Pro Python environment via `arcpy`. EarthForge uses the same GDAL binaries — no conflict.

## Verify Installation

```python
# In ArcGIS Pro's built-in Notebook or the Python Window
import earthforge.core
print(earthforge.core.__version__)

from earthforge.stac.search import search_catalog
print("EarthForge ready")
```

## Example: Fetch Imagery and Add to Map

```python
import arcpy
import asyncio
from pathlib import Path
from earthforge.core.config import EarthForgeProfile
from earthforge.stac.search import search_catalog
from earthforge.stac.fetch import fetch_assets

# Use KyFromAbove STAC directly (no config file needed)
profile = EarthForgeProfile(
    name="kyfromabove",
    stac_api="https://spved5ihrl.execute-api.us-west-2.amazonaws.com/",
    storage_backend="local",
)

# Get the current map extent as a bbox
aprx = arcpy.mp.ArcGISProject("CURRENT")
extent = aprx.activeMap.defaultCamera.getExtent()
bbox = [extent.XMin, extent.YMin, extent.XMax, extent.YMax]

async def fetch_imagery():
    results = await search_catalog(
        profile, collections=["orthos-phase3"], bbox=bbox, max_items=3
    )
    print(f"Found {len(results.items)} tiles in current extent")

    for item in results.items[:1]:  # fetch first tile
        item_url = f"{profile.stac_api}collections/{item.collection}/items/{item.id}"
        result = await fetch_assets(
            profile,
            item_url=item_url,
            output_dir=str(Path(arcpy.env.scratchFolder) / "orthos"),
            assets=["thumbnail"],
        )
        print(f"Downloaded: {result.files[0].local_path}")
        return result.files[0].local_path

local_path = asyncio.run(fetch_imagery())

# Add the downloaded file to the ArcGIS Pro map
aprx.activeMap.addDataFromPath(local_path)
print(f"Added {local_path} to map")
```

## Example: Convert Shapefile to GeoParquet

```python
import asyncio
from earthforge.vector.convert import convert_vector
from earthforge.vector.info import inspect_vector

async def convert_and_inspect(shp_path, output_dir):
    result = await convert_vector(shp_path, output=f"{output_dir}/output.parquet")
    print(f"Converted {result.feature_count} features → {result.output}")
    print(f"CRS: {result.crs}, Bbox: {result.bbox}")

    # Verify the output
    info = await inspect_vector(result.output)
    print(f"GeoParquet: {info.row_count} rows, {info.num_columns} columns")
    return result.output

output = asyncio.run(convert_and_inspect(
    r"C:\data\parcels.shp",
    r"C:\data\parquet"
))

# The GeoParquet can now be added to ArcGIS Pro as a layer
aprx.activeMap.addDataFromPath(output)
```

## Example: Validate COGs Before Adding to Project

ArcGIS Pro can read COGs, but invalid COGs (strip layout, no overviews) will be slow. Validate first:

```python
import asyncio
from earthforge.raster.validate import validate_cog

async def check_cog(path):
    result = await validate_cog(path)
    if result.is_valid:
        print(f"Valid COG: {path}")
        return True
    else:
        print(f"Invalid COG: {path}")
        for check in result.checks:
            if not check.passed:
                print(f"  FAIL: {check.name} — {check.message}")
        return False

is_valid = asyncio.run(check_cog(r"C:\data\scene.tif"))
if is_valid:
    aprx.activeMap.addDataFromPath(r"C:\data\scene.tif")
```

## ArcGIS Notebook Integration

EarthForge works naturally in ArcGIS Pro Notebooks. Use `await` directly in notebook cells (Jupyter supports `await` at the top level):

```python
# ArcGIS Pro Notebook cell — no asyncio.run() needed
from earthforge.core.config import EarthForgeProfile
from earthforge.stac.search import search_catalog

profile = EarthForgeProfile(
    name="default",
    stac_api="https://earth-search.aws.element84.com/v1",
    storage_backend="local",
)

results = await search_catalog(
    profile,
    collections=["sentinel-2-l2a"],
    bbox=(-85.0, 37.0, -84.0, 38.0),
    max_items=5,
)
print(f"Found {len(results.items)} Sentinel-2 scenes over Kentucky")
```

## Notes

- The ArcGIS Pro Python environment is `C:\Program Files\ArcGIS\Pro\bin\Python\envs\arcgispro-py3`
- EarthForge's async functions work in ArcGIS Notebooks via Jupyter's built-in event loop
- For scripts run via ArcPy (not notebooks), use `asyncio.run()` as shown above
- GDAL version in ArcGIS Pro may differ from standalone installations — EarthForge uses GDAL via `osgeo` which ArcGIS Pro provides
