ArcGIS Python API
Authentication
from arcgis.gis import GIS
# ArcGIS Notebooks (always use this in hosted notebooks)
gis = GIS("home")
# OAuth (interactive apps)
gis = GIS("https://org.maps.arcgis.com", client_id="APP_ID")
# API Key (public content only)
gis = GIS(api_key="AAPK...")
# Username/password (scripts only — use environment variables)
import os
gis = GIS("https://org.maps.arcgis.com", os.environ["AGOL_USER"], os.environ["AGOL_PASS"])
Feature Layer Essentials
from arcgis.features import FeatureLayer, FeatureLayerCollection
# From URL
fl = FeatureLayer("https://services.arcgis.com/.../FeatureServer/0", gis=gis)
# From Item
item = gis.content.get("ITEM_ID")
flc = FeatureLayerCollection.fromitem(item)
fl = flc.layers[0] # First layer
table = flc.tables[0] # First table
# Layer properties
print(fl.properties.name, fl.properties.geometryType)
print([f["name"] for f in fl.properties.fields])
Query Patterns
# Basic query → Spatially Enabled DataFrame
sdf = fl.query(where="status='Active'", out_fields="name,status").sdf
# All records (small datasets <10k)
sdf = fl.query(where="1=1", return_all_records=True).sdf
# Pagination (large datasets) — SEE references/large-datasets.md
count = fl.query(return_count_only=True)
# Spatial query
from arcgis.geometry import Envelope
bbox = Envelope({"xmin": -78, "ymin": 35, "xmax": -77, "ymax": 36, "spatialReference": {"wkid": 4326}})
sdf = fl.query(geometry_filter=bbox, spatial_rel="intersects").sdf
# Return geometry or not
sdf = fl.query(return_geometry=False).sdf # Faster for attribute-only queries
# Statistics
stats = fl.query(where="1=1", group_by_fields_for_statistics="region",
out_statistics=[{"statisticType": "count", "onStatisticField": "OBJECTID", "outStatisticFieldName": "total"}])
Edit Features
from arcgis.features import Feature
from arcgis.geometry import Point
# Add
new_features = [
Feature(geometry=Point({"x": -77.0, "y": 38.9, "spatialReference": {"wkid": 4326}}),
attributes={"name": "Site A", "status": "Active"})
]
result = fl.edit_features(adds=new_features)
# Update (must include OBJECTID)
updates = [Feature(attributes={"OBJECTID": 1, "status": "Closed"})]
result = fl.edit_features(updates=updates)
# Delete
fl.edit_features(deletes="1,2,3") # By OID string
# Or by query
oids = fl.query(where="status='Inactive'", return_ids_only=True)["objectIds"]
if oids:
fl.edit_features(deletes=",".join(map(str, oids)))
Batch Edits (Chunk for Reliability)
def chunked_edit(fl, features, operation="updates", chunk_size=200):
"""Batch edit with chunking to avoid timeouts."""
results = []
for i in range(0, len(features), chunk_size):
chunk = features[i:i + chunk_size]
result = fl.edit_features(**{operation: chunk})
results.append(result)
print(f"Processed {min(i + chunk_size, len(features))}/{len(features)}")
return results
Spatially Enabled DataFrame (SEDF)
import pandas as pd
from arcgis.features import GeoAccessor, GeoSeriesAccessor
# Query to SEDF
sdf = fl.query().sdf
# DataFrame to SEDF
df = pd.DataFrame({"name": ["A", "B"], "lat": [38.9, 39.0], "lon": [-77.0, -77.1]})
sdf = pd.DataFrame.spatial.from_xy(df, "lon", "lat", sr=4326)
# From GeoJSON/Shapefile
sdf = pd.DataFrame.spatial.from_featureclass("path/to/data.shp")
# Export
sdf.spatial.to_featureclass("output.shp")
sdf.spatial.to_featurelayer("New Layer", gis=gis, folder="Analysis")
# Spatial operations
sdf["SHAPE"].geom.buffer(1000) # Buffer geometries
sdf.spatial.project(3857) # Reproject
Geometry Objects
from arcgis.geometry import Point, Polyline, Polygon, Geometry
from arcgis.geometry import project, buffer, union, intersect
# Create geometries (always include spatialReference)
pt = Point({"x": -77, "y": 38.9, "spatialReference": {"wkid": 4326}})
line = Polyline({"paths": [[[-77, 38], [-76, 39]]], "spatialReference": {"wkid": 4326}})
poly = Polygon({"rings": [[[-77, 38], [-76, 38], [-76, 39], [-77, 39], [-77, 38]]],
"spatialReference": {"wkid": 4326}})
# Geometry operations
buffered = buffer([pt], in_sr=4326, distances=[1000], unit="Meters")[0]
projected = project([pt], in_sr=4326, out_sr=3857)[0]
# From GeoJSON
geom = Geometry({"type": "Point", "coordinates": [-77, 38.9]})
Web Maps
from arcgis.mapping import WebMap
import json
# Load
item = gis.content.get("WEBMAP_ID")
wm = WebMap(item)
# Inspect layers
for layer in wm.layers:
print(layer.title, layer.url)
# Get raw definition for modification
data = item.get_data() # Returns dict
# Modify and save
data["operationalLayers"][0]["visibility"] = False
item.update(data=json.dumps(data))
# Create new web map
from arcgis.mapping import WebMap
wm = WebMap()
wm.add_layer(fl)
wm.save(item_properties={"title": "New Map", "tags": "analysis"})
Content Management
# Search
results = gis.content.search(f'title:"My Layer" owner:{gis.users.me.username} type:"Feature Service"')
# Create/publish
csv_item = gis.content.add({"title": "Data", "type": "CSV"}, data="data.csv")
published = csv_item.publish()
# Update
item.update(item_properties={"description": "Updated"})
item.update(data="new_data.csv") # Replace data
# Share
item.share(org=True)
item.share(groups=["GROUP_ID"])
item.share(everyone=True)
# Move/delete
item.move(folder="Archive")
item.delete()
Common Patterns
Check Before Create
def get_or_create_item(gis, title, item_type, create_func):
results = gis.content.search(f'title:"{title}" owner:{gis.users.me.username} type:"{item_type}"')
if results:
return results[0]
return create_func()
Error Handling
from arcgis.gis import GISError
try:
result = fl.edit_features(updates=features)
# Check for partial failures
for r in result.get("updateResults", []):
if not r.get("success"):
print(f"Failed OID {r.get('objectId')}: {r.get('error')}")
except GISError as e:
print(f"API Error: {e}")
NaN Handling
import pandas as pd
import numpy as np
# Convert NaN to None before edit_features
def clean_for_agol(df):
return df.replace({np.nan: None, pd.NaT: None})
Reference Files
- Large dataset patterns → See
references/large-datasets.md
- Geocoding & routing → See
references/geocoding-routing.md
- Spatial analysis tools → See
references/spatial-analysis.md
- Admin & user management → See
references/admin.md