15.1 Python for GIS Automation: Scripting, Spatial DataFrames & Geospatial Libraries

Key Takeaways

  • Automation should stream or batch records where practical, restrict requested fields, manage database resources deterministically, validate inputs, and log failures so workflows remain efficient and reproducible.
  • The open-source geospatial Python stack unifies low-level C/C++ engines through clean APIs: GDAL for raster operations, OGR and Fiona for vector data input/output, Shapely for 2D computational geometry and DE-9IM predicates, and PyProj for cartographic projections.
  • GeoPandas extends pandas DataFrames into spatial tables with an active geometry column (GeoSeries), enabling coordinate reprojection, spatial indexing (R-tree/STRtree), spatial joins (sjoin), and geometric overlays (overlay).
  • Spatial joins (sjoin) transfer tabular attributes based on topological predicates (intersects, within, contains) without altering geometric shapes, whereas spatial overlays (overlay) compute boolean set intersections, unions, and differences that slice and decompose geometries.
  • Esri's ArcPy Data Access module (arcpy.da) optimizes performance and database concurrency through SearchCursor, UpdateCursor, and InsertCursor wrapped in Python context managers (with statements) and specialized geometry tokens (SHAPE@, SHAPE@XY, SHAPE@AREA).
Last updated: September 2026

15.1 Python for GIS Automation: Scripting, Spatial DataFrames & Geospatial Libraries

Core Principle: Geospatial programming transforms manual, click-driven GIS desktop workflows into reproducible, scalable, and automated processing pipelines. In modern GIS practice, Python 3 serves as the universal automation lingua franca. Professional mastery requires distinguishing between open-source computational stacks (GDAL/OGR, Shapely, Fiona, Rasterio, GeoPandas) and platform-specific geoprocessing frameworks (such as Esri's arcpy). Understanding memory management, geometry representations, coordinate reference system propagation, and database locking mechanics during cursor iteration is critical for GISP candidates.

Vendor-neutral exam note: ArcPy, GeoPandas, Fiona, Shapely, and PyProj are useful implementation examples. The blueprint tests transferable coding and application-development concepts, not memorization of a particular product API.


1. Python as the Primary Geospatial Automation Language

Python's ascendancy in geographic information science is rooted in its readable syntax, extensive scientific computing ecosystem, and direct integration into both proprietary enterprise platforms and open-source spatial distributions.

   +-------------------------------------------------------------------------+
   |                  GEOSPATIAL PYTHON ARCHITECTURE LAYERS                  |
   +-------------------------------------------------------------------------+
   | High-Level Vector & Raster APIs:                                        |
   |   - GeoPandas (Spatial DataFrames, sjoin, overlay, spatial indices)     |
   |   - Rasterio (Pythonic raster arrays, windowed reads, affine transforms) |
   |   - ArcPy / arcpy.da (Geoprocessing workflows, enterprise cursors)      |
   +-------------------------------------------------------------------------+
   | Computational Geometry & Coordinate Transformations:                    |
   |   - Shapely (GEOS wrapper: 2D geometry, buffer, union, intersection)   |
   |   - PyProj (PROJ wrapper: geodetic transformations, datum shifts)        |
   +-------------------------------------------------------------------------+
   | Low-Level I/O Drivers & C/C++ Foundation Libraries:                     |
   |   - GDAL (Geospatial Data Abstraction Library - Raster formats)         |
   |   - OGR (Simple Features Library - Vector formats)                      |
   |   - GEOS (Geometry Engine, Open Source) / PROJ (Cartographic Projections)|
   +-------------------------------------------------------------------------+

Python 3 Syntax Foundations & Control Structures

Geospatial pipelines frequently process millions of geographic records, making clean control structures, memory management, and idiomatic syntax paramount:

  • Iteration and Comprehensions: While conventional for loops iterate over layers and feature classes, list comprehensions and dictionary comprehensions provide compact, optimized execution for filtering and transforming attributes:
    # List comprehension filtering valid polygon feature classes
    valid_layers = [lyr for lyr in feature_classes if lyr.endswith("_poly")]
    
    # Dictionary comprehension extracting attribute key-value lookups
    code_lookup = {row[0]: row[1] for row in cursor_records if row[1] is not None}
    
  • Generator Expressions: When iterating over massive datasets, loading all geometries into memory causes out-of-memory crashes. Generator expressions ((expr for item in iterable)) yield features lazily one at a time, keeping memory consumption constant regardless of feature count.
  • Structured Exception Handling: Production geoprocessing tools must anticipate file-lock conflicts, missing projection definitions, corrupt geometries, and network drops. Structured error handling utilizes try, except, else, and finally blocks:
    try:
        arcpy.management.Buffer("streams.shp", "streams_buf.shp", "50 Meters")
    except arcpy.ExecuteError:
        # Capture geoprocessing engine errors and system messages
        print(arcpy.GetMessages(severity=2))
    except Exception as err:
        # Capture standard Python exceptions
        print(f"General script failure: {err}")
    else:
        print("Buffer completed successfully without errors.")
    finally:
        # Always clean up temporary layers or release scratch locks
        arcpy.management.Delete("in_memory")
    

Virtual Environments and Geospatial C-Libraries

Geospatial Python packages rely heavily on underlying compiled C/C++ shared libraries (specifically GDAL, GEOS, PROJ, and libspatialindex). Installing packages using standard pip can lead to compilation failures or dynamic linking incompatibilities on host operating systems.

  • Virtual Environments (venv): Python's built-in venv module isolates package dependencies between projects, preventing package version collisions.
  • Conda and conda-forge: In scientific and geospatial computing, Conda serves as a cross-platform binary package manager. Conda packages distribute pre-compiled C/C++ binaries alongside Python wrappers. Geospatial professionals routinely configure the community-driven conda-forge channel with strict channel priority to avoid mixing incompatible runtime builds of GDAL and PROJ:
    conda config --add channels conda-forge
    conda config --set channel_priority strict
    conda create -n geo_env python=3.11 geopandas rasterio pyproj
    

2. The Open-Source Geospatial Python Stack

The open-source Python geospatial ecosystem is built on a modular division of responsibilities, where specialized libraries handle vector I/O, raster arrays, planar geometry, and geodetic coordinate transformations.

GDAL and OGR: The Foundation Drivers

The Geospatial Data Abstraction Library (GDAL) and its vector counterpart OGR constitute the primary translation engine across the GIS industry. Distributed under the osgeo package:

  • osgeo.gdal: Provides format drivers for virtually all raster formats (GeoTIFF, Cloud-Optimized GeoTIFF, HDF5, NetCDF, MrSID). It exposes datasets, raster bands, color tables, and geotransform arrays.
    • Affine Geotransform Array: A six-element tuple mapping pixel coordinates $(col, row)$ to map coordinates $(X, Y)$: [XY]=[GT[0]+col⋅GT[1]+row⋅GT[2]GT[3]+col⋅GT[4]+row⋅GT[5]]\begin{bmatrix} X \\ Y \end{bmatrix} = \begin{bmatrix} GT[0] + col \cdot GT[1] + row \cdot GT[2] \\ GT[3] + col \cdot GT[4] + row \cdot GT[5] \end{bmatrix} Where $GT[0]$ is the upper-left $X$ coordinate, $GT[3]$ is the upper-left $Y$ coordinate, $GT[1]$ is pixel width (resolution), $GT[5]$ is pixel height (typically negative), and $GT[2], GT[4]$ represent rotational skew (zero for north-up rasters).
  • osgeo.ogr: Reads and writes vector geometries across dozens of drivers (Shapefile, GeoPackage, PostGIS, KML, GML). OGR manages DataSource instances containing Layer objects, allowing direct attribute and spatial filtering using SetSpatialFilterRect() or SetAttributeFilter().

Shapely: 2D/3D Planar Computational Geometry

Shapely wraps the C++ GEOS (Geometry Engine, Open Source) library to provide planar topological predicates and geometric constructors.

  • Primitives: Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon, and LinearRing.
  • Topological Predicates: Implements standard Open Geospatial Consortium (OGC) Dimensionally Extended 9-Intersection Model (DE-9IM) tests: .intersects(), .contains(), .within(), .touches(), .crosses(), .overlaps(), .disjoint(), and .equals().
  • Constructive Operations: Computes geometric set operations: .buffer(), .intersection(), .union(), .difference(), .symmetric_difference(), and .simplify().
  • Serialization: Parses and exports geometries via Well-Known Text (WKT), Well-Known Binary (WKB), and GeoJSON dictionary mappings (__geo_interface__).

[!IMPORTANT] The Fundamental Limitation of Shapely: Shapely is strictly a planar geometry library. Shapely has zero awareness of Coordinate Reference Systems (CRS), datums, or map projections. Coordinates in Shapely are treated purely as numbers on a Cartesian plane. If you ask Shapely to compute .buffer(1.0) on a geometry with coordinates stored in decimal degrees (e.g., WGS 84), Shapely will buffer the feature by 1.0 angular degree (~111 km at the equator), not 1.0 meter! All reprojections must be handled externally before passing coordinates to Shapely.

Fiona and Rasterio: Idiomatic Pythonic I/O

While raw GDAL/OGR bindings use direct C-style pointers, Fiona and Rasterio provide modern, pythonic wrappers:

  • Fiona: Manages vector file reading and writing. Features are read as standard Python dictionaries matching the GeoJSON specification, containing 'geometry' and 'properties' keys. Fiona integrates cleanly with Python context managers (with fiona.open(...) as src:), ensuring file locks and dataset headers are safely closed upon loop exit.
  • Rasterio: Handles raster operations by pairing GDAL's format drivers with NumPy N-dimensional arrays (ndarray). It allows reading specific rectangular pixel windows (Window(col_off, row_off, width, height)) without loading massive multiterabyte rasters into RAM, computing focal or map algebra operations on NumPy matrices, and writing out georeferenced GeoTIFFs with automated affine metadata.

PyProj: Cartographic Projections and Transformations

PyProj wraps the PROJ cartographic projection engine, managing geodetic conversions and coordinate transformations:

  • Manages Coordinate Reference System definitions via EPSG codes, WKT2 strings, or PROJ strings through the CRS class.
  • Executes forward and inverse transformations between geographic (latitude/longitude) and projected (easting/northing) coordinate systems via Transformer.from_crs().
  • Correctly accounts for 3D datum transformations, coordinate epoch shifts, and horizontal/vertical datum conversions (such as transforming between NAD83 and WGS84).

3. GeoPandas and Spatial DataFrames

GeoPandas is the core high-level data science library for vector GIS in Python. It extends the popular pandas tabular data analysis library by introducing spatial data types and geometric operations.

   +-------------------------------------------------------------------------+
   |                          GeoDataFrame STRUCTURE                         |
   +---------+---------------+-------------+---------------------------------+
   | PARCEL  | OWNER         | ASSESSED    | geometry (Active GeoSeries)     |
   +---------+---------------+-------------+---------------------------------+
   | 101     | Acme Corp     | 450000      | POLYGON ((500 200, 550 200...)) |
   | 102     | Baker Trust   | 320000      | POLYGON ((550 200, 600 200...)) |
   | 103     | City Transit  | 0           | POLYGON ((600 200, 650 200...)) |
   +---------+---------------+-------------+---------------------------------+
        |           |              |                       |                 
   [ Standard Pandas Series / Columns ]     [ Shapely Geometry Objects ]     
                                            [ + crs attribute (e.g. EPSG:2277)|

GeoDataFrame and GeoSeries Architecture

A GeoDataFrame is a subclass of pandas.DataFrame. It behaves identically to a pandas DataFrame (supporting grouping, filtering, aggregating, joining, and pivoting) with two crucial spatial additions:

  1. geometry column: A specialized GeoSeries holding Shapely geometric objects.
  2. crs attribute: A PyProj CRS object defining the Coordinate Reference System of the geometry column.

Reprojecting an entire dataset is executed via .to_crs():

import geopandas as gpd

# Read vector layer and inspect CRS
gdf = gpd.read_file("parcels.gpkg")

# Reproject from WGS84 (EPSG:4326) to Texas State Plane Central (EPSG:2277)
gdf_projected = gdf.to_crs(epsg=2277)

# Compute accurate metric area
gdf_projected["area_sqm"] = gdf_projected.geometry.area

Spatial Indexing (STRtree / R-tree)

Iterating through $N$ features in Layer A and testing spatial relationships against $M$ features in Layer B requires $N \times M$ pairwise comparisons ($O(N \times M)$ complexity). For large datasets, this approach is computationally prohibitive.

GeoPandas leverages spatial indexing using an STRtree (Sort-Tile-Recursive R-tree). A spatial index organizes geometries into a hierarchical bounding box tree. When a query is executed, the index eliminates features whose bounding boxes do not intersect the search geometry in $O(\log M)$ time, reserving expensive exact DE-9IM geometric evaluation only for candidates passing the bounding box test.

Spatial Joins (gpd.sjoin) vs. Spatial Overlays (gpd.overlay)

A foundational distinction on the GISP exam is the difference between a Spatial Join and a Spatial Overlay:

   SPATIAL JOIN (sjoin)                     SPATIAL OVERLAY (overlay - intersection)
   ---------------------------------        ---------------------------------
   Attributes combined;                     Geometries physically sliced;
   Geometries remain UNMODIFIED.            New decomposed polygons created.
   
   Layer A (Parcels)   Layer B (Flood Zone) Layer A (Parcels)   Layer B (Flood Zone)
   +-------+-------+   +---------------+    +-------+-------+   +---------------+   
   |   1   |   2   |   |     Zone A    |    |   1   |   2   |   |     Zone A    |
   +-------+-------+   +---------------+    +-------+-------+   +---------------+   
           |                   |                    |                   |           
           v                   v                    v                   v           
   OUTPUT GeoDataFrame:                     OUTPUT GeoDataFrame:                    
   +----+----------+--------------------+   +----+----------+-----------------------+
   | ID | FLOOD    | geometry           |   | ID | FLOOD    | geometry              |
   +----+----------+--------------------+   +----+----------+-----------------------+
   | 1  | Zone A   | Full Parcel 1 Geom |   | 1  | Zone A   | Parcel 1 inside Flood |
   | 2  | Zone A   | Full Parcel 2 Geom |   | 2  | Zone A   | Parcel 2 inside Flood |
   +----+----------+--------------------+   +----+----------+-----------------------+
  1. Spatial Join (gpd.sjoin):
    • Mechanism: Transfers attribute columns from a right GeoDataFrame into a left GeoDataFrame based on a topological predicate (intersects, within, contains).
    • Geometry Outcome: The original geometries of the target layer are completely preserved. No lines are split, no polygons are clipped, and no new vertices are created.
    • Join Types: Supports how='inner', how='left', and how='right'.
  2. Spatial Overlay (gpd.overlay):
    • Mechanism: Computes planar geometric set-theoretic intersections, unions, differences, or symmetric differences between two polygon GeoDataFrames.
    • Geometry Outcome: Geometries are physically sliced, clipped, and restructured. New geometric boundaries and vertices are generated wherever input features intersect.
    • Overlay Operations:
      • how='intersection': Keeps only areas where both layers overlap; attributes from both are merged.
      • how='union': Keeps all areas from both layers, creating new split polygon fragments.
      • how='difference': Retains areas of the first layer that do not overlap the second layer.
      • how='symmetric_difference': Retains areas belonging to either layer, but excludes overlapping areas.
      • how='identity': Retains all geometry of the input layer, but splits it where it overlaps the identity layer, appending attributes.

4. Esri ArcPy Module & Enterprise Geoprocessing

In enterprise environments utilizing Esri software, automation is governed by the ArcPy site package. ArcPy exposes geoprocessing tools, raster algebra, mapping cartography, and direct database access.

Geoprocessing Environment Settings (arcpy.env)

Global workflow parameters are managed through arcpy.env. Key environmental properties include:

  • arcpy.env.workspace: Sets the default input/output workspace (directory, file geodatabase .gdb, or enterprise .sde connection).
  • arcpy.env.scratchWorkspace: Specifies a designated sandbox location for temporary intermediate scratch data.
  • arcpy.env.overwriteOutput = True: Authorizes geoprocessing tools to overwrite existing datasets without throwing runtime errors.
  • arcpy.env.outputCoordinateSystem: Forces output datasets to be reprojected into a designated CRS during geoprocessing execution.
  • arcpy.env.extent: Constrains geoprocessing operations to a specific spatial bounding box.

The Data Access Module (arcpy.da) & Cursors

Prior to the release of the arcpy.da (Data Access) module, table iteration in ArcPy relied on legacy cursors (arcpy.SearchCursor), which were notoriously slow and prone to leaving orphaned file locks. The arcpy.da module bypasses the COM geoprocessing overhead, providing direct low-level C++ record access with up to a 10x performance improvement.

There are three cursor classes in arcpy.da:

  1. arcpy.da.SearchCursor: Read-only access to records and geometries.
  2. arcpy.da.UpdateCursor: Allows modifying attribute values (cursor.updateRow(row)) or deleting records (cursor.deleteRow()).
  3. arcpy.da.InsertCursor: Appends new records and features to an existing table or feature class (cursor.insertRow(row)).
import arcpy

# Configure environment
arcpy.env.workspace = r"C:/GIS/CityData.gdb"
arcpy.env.overwriteOutput = True

# Update Cursor with Context Manager to prevent orphaned database locks
fields = ["PARCEL_ID", "ASSESSED_VALUE", "TAX_DISTRICT", "SHAPE@AREA"]
with arcpy.da.UpdateCursor("CadastralParcels", fields, where_clause="TAX_DISTRICT = 'D-1'") as cursor:
    for row in cursor:
        # row is a mutable list matching the fields parameter index
        parcel_id, assessed_val, district, area_sqm = row
        
        if area_sqm > 10000:
            # Apply 5% surcharge to large commercial parcels
            row[1] = assessed_val * 1.05
            cursor.updateRow(row)
        elif assessed_val == 0:
            # Delete erroneous records
            cursor.deleteRow()

Python Context Managers (with Statements) & File Locking

In multi-user geodatabases and local file geodatabases, opening a cursor places a schema lock or shared read/write lock on the underlying table. If a script encounters an unhandled exception or terminates without explicitly deleting cursor variables (del cursor, row), the file lock persists in memory, preventing subsequent operations or other users from modifying the geodatabase.

Using Python context managers (with arcpy.da.UpdateCursor(...) as cursor:) ensures normal context cleanup runs when the block exits, including ordinary exceptions; abrupt process termination or external failures can still leave resources for the platform to recover.

ArcPy Geometry Tokens

Extracting entire geometry representations as full COM objects incurs significant serialization overhead. When writing cursors, developers access specific geometry properties using geometry tokens:

TokenReturn Data TypeDescription
SHAPE@arcpy.GeometryFull geometry object (provides .buffer(), .contains(), .JSON, etc.).
SHAPE@XYtuple (x, y)Centroid coordinates as a lightweight float tuple.
SHAPE@TRUECENTROIDtuple (x, y)Exact center of gravity centroid for complex/concave polygons.
SHAPE@AREAfloatFeature area in the linear units of the dataset's coordinate system.
SHAPE@LENGTHfloatPerimeter (polygon) or linear distance (polyline) in linear units.
SHAPE@X, SHAPE@YfloatCoordinate values for point feature classes.
SHAPE@JSONstringEsri JSON geometry representation.
SHAPE@WKTstringOGC Well-Known Text geometry representation.
OID@intUnique Object Identifier / Primary Key value.

Using SHAPE@XY or SHAPE@AREA instead of pulling the full SHAPE@ object accelerates cursor execution by orders of magnitude when only geometric coordinates or dimensions are needed.


5. Comparative Reference Tables

Open-Source Python Stack vs. ArcPy Ecosystem

Functional RequirementOpen-Source Python StackEsri ArcPy Platform
Vector File I/Ofiona.open(), geopandas.read_file()arcpy.management.CopyFeatures(), arcpy.da
Raster File I/Orasterio.open(), osgeo.gdal.Open()arcpy.Raster(), arcpy.da.NumPyArrayToRaster()
2D Geometry Operationsshapely.geometry (Planar only)arcpy.Geometry, arcpy.Point, arcpy.Polygon
Coordinate Transformspyproj.Transformer, gdf.to_crs()arcpy.management.Project(), arcpy.SpatialReference
Spatial Joinsgeopandas.sjoin(left, right, predicate=...)arcpy.analysis.SpatialJoin()
Geometric Overlaysgeopandas.overlay(df1, df2, how=...)arcpy.analysis.Intersect(), arcpy.analysis.Union()
Database CursorsSQLAlchemy, psycopg2, GeoPandas SQL readarcpy.da.SearchCursor, UpdateCursor, InsertCursor
Licensing RequirementFree, Permissive Open-Source (BSD/MIT)Commercial License (ArcGIS Pro / Server runtime)

ArcPy Data Access (arcpy.da) Cursor Comparison

Cursor TypePrimary MethodRead / WriteTable Locking BehaviorTypical Geospatial Use Case
SearchCursorRead iterationRead-onlyShared read lockReading values, spatial filtering, exporting attribute tables.
UpdateCursor.updateRow(), .deleteRow()Read & WriteExclusive write lockCalculating fields, updating coordinates, cleaning dirty records.
InsertCursor.insertRow()Write-onlyExclusive write lockAppending newly digitized features, batch loading external records.

6. Practical Geospatial Scenario: Municipal Automated Stormwater Impervious Surface Analysis

Scenario Context

A municipal stormwater utility must process thousands of building footprints against updated aerial LiDAR vegetation masks to calculate impervious surface fees. The workflow requires:

  1. Reading building footprints from an enterprise geodatabase.
  2. Iterating through each parcel to calculate structural building footprint area.
  3. Calculating the percentage of parcel area covered by structures.
  4. Updating an IMPERVIOUS_FEE column based on assessed coverage tiers.

Script Implementation & Lock Management

import arcpy
import sys

arcpy.env.workspace = r"C:/GIS/Enterprise_Stormwater.gdb"
arcpy.env.overwriteOutput = True

fc_parcels = "TaxParcels"
fields = ["PARCEL_ID", "SHAPE@AREA", "BLDG_AREA", "IMPERVIOUS_FEE"]

try:
    # Using context manager to prevent persistent schema locking
    with arcpy.da.UpdateCursor(fc_parcels, fields) as cursor:
        for row in cursor:
            parcel_id, parcel_area, bldg_area, fee = row
            
            if parcel_area <= 0:
                continue  # Skip invalid geometries
                
            # Calculate ratio
            impervious_ratio = (bldg_area / parcel_area) * 100.0
            
            # Apply tiered stormwater utility rate
            if impervious_ratio > 60.0:
                row[3] = 450.00  # High tier
            elif impervious_ratio > 30.0:
                row[3] = 250.00  # Medium tier
            else:
                row[3] = 100.00  # Low baseline tier
                
            cursor.updateRow(row)
            
    print("Stormwater fee calculations successfully updated.")

except arcpy.ExecuteError:
    arcpy.AddError(arcpy.GetMessages(2))
    sys.exit(1)
except Exception as e:
    print(f"Standard Python execution error: {e}")
    sys.exit(1)

7. Common Exam Traps & Pitfalls

[!CAUTION] Exam Trap 15.1.1: Legacy arcpy.SearchCursor vs. Modern arcpy.da.SearchCursor. Exam questions frequently test cursor syntax and performance. Legacy cursors (arcpy.SearchCursor) return full feature COM objects, do not support geometry tokens like SHAPE@AREA, and must be explicitly deleted using del cursor to release locks. Modern cursors (arcpy.da.SearchCursor) return lightweight tuples, accept field name lists and geometry tokens, and should always be paired with a Python with statement context manager.

[!CAUTION] Exam Trap 15.1.2: The Shapely Coordinate Reference System Blindness Trap. Never assume Shapely can reproject data or calculate geodesic distances on unprojected coordinates. Shapely operates strictly in mathematical Cartesian space. If an exam question asks which Python library transforms a GeoJSON dataset from WGS 84 (EPSG:4326) to UTM Zone 18N (EPSG:32618), the answer is PyProj (or GeoPandas wrapping PyProj), never Shapely.

[!CAUTION] Exam Trap 15.1.3: Spatial Join vs. Spatial Overlay Geometric Consequences. A recurring GISP trap asks which operation to select when you need to calculate the exact square meters of forest stands inside a national park boundary where the stands cross the park fence. Selecting a Spatial Join (sjoin) is wrong because sjoin preserves the full original polygon geometry of the forest stands, merely attaching the park's attributes. The correct operation is a Spatial Overlay (overlay with intersection), which physically clips and decomposes the forest polygons along the park boundary.

Loading diagram...
ArcPy Data Access Cursors & Open-Source Geospatial Python Pipeline
Test Your Knowledge

A script must update hundreds of thousands of parcel rows without loading the full table into memory and must release database resources even if an exception occurs. Which design is best?

A
B
C
D
Test Your Knowledge

Two polygon layers must be physically sliced at their shared boundaries, producing new intersection geometries with attributes from both inputs. Which operation is required?

A
B
C
D
Test Your Knowledge

In a geospatial scripting stack, which separation of responsibilities is sound?

A
B
C
D