Skip To Content ArcGIS for Developers Sign In Dashboard

ArcGIS API for Python

Part 3 - Spatial operations on geometries

Previously, in Part 1 of this guide series to arcgis.geometry module, you have seen the introduction to the module and some foundational concepts. In Part 2, you learned how to create geometry objects, their properties, and how to work with one, including its interactions with map widgets. In this part, let's continue to explore spatial operations of geometry objects using two different usage patterns.

Spatial Operations

What are spatial operations? Spatial operations are functions that "create new spatial data from specified input data", e.g. union, difference, symmetric difference, and intersection, and can be used for a variety of spatial analyses. For example, government authorities may use the intersect operation to determine whether a proposed road cuts through a restricted piece of land such as a nature reserve or a private property.

Chained operations are even more powerful. For example, in order to identify food deserts within an urban area, the analysis might begin by performing a union operation to the service areas of grocery stores, farmer's markets, and food co-ops. Then, taking the difference between this single geometry of all services areas and that of a polygon delineating a neighborhood would reveal the areas within that neighborhood where access to healthy, whole foods may not exist.

Two patterns of applying spatial operations

There are two ways of performing spatial operations

  • (a) Object Oriented Programming (OOP) pattern - here you create a geometry object first, then call methods off the geometry object, and
  • (b) calling functions from arcgis.geometry.functions directly without initiating any geometry objects, e.g. the from_geo_coordinate_string or to_geo_coordinate_string methods, which takes spatial data as input, analyzes the data, then produces output data that is the derivative of the analysis performed on the input data.

The major difference between these two is that the OOP pattern uses local geometry engines such as shapely or arcpy, while calling functions from arcgis.geometry.functions will use server-side geometry engine by sending the geometries over to the Geometry Service configured with your web GIS server. If your web GIS is ArcGIS Online, the latter pattern requires credits. Further, the latter pattern is not performant for larger datasets. Users are recommended to only use (b) if they do not have access to either of the local geometry engines. Further more, if you want to process geometries on the server, rather than applying (b), you can also publish the data as feature service and then run analyses using arcgis.feature module.

The rest of this page demonstrates a set of spatial operations performed using both these patterns.

a. OOP Pattern - Uses local geometry engine

Before spatial operations are being demonstrated, let us first import necesary libraries, and create a GIS instance that connects to ArcGIS Online.

Note: If you are running this guide in an environment without arcpy, you can comment out the line that imports it and proceed with the guide. The Python API will look for shapely library and use it for geometry operations.
Note: When using Jupyter/ArcGIS Notebooks interface, you can quickly visualize a geometry object by just querying it in a cell. This lets the notebook know to display the geometry as a SVG image. If you are running this guide in an environment with arcpy, then to visualize the geometries as SVG graphics, replace the cells that query the object with geom_object.as_arcpy
In [2]:
from arcgis.gis import GIS
from arcgis.geometry import Polygon, Geometry, Point, Polyline
from arcgis.geocoding import geocode
import arcpy
In [3]:
gis = GIS('home')

a1. Union

The first method, <first geom object>.union(<second_geometry>), can be called off from a geometry object to construct the geometry that is the set-theoretic union of the input geometries. You are going to see an example of how to create a union of two polygons (e.g. geometry1 and geometry2):

In [4]:
geom1_json = {'spatialReference': {'latestWkid': 3857, 'wkid': 102100}, 'rings': [[[-8232647.749922129, 4978983.410862541], [-8232389.7749516675, 4978840.091434507], [-8232762.405464557, 4978161.712808477], [-8233001.2711779475, 4978295.477607976], [-8232647.749922129, 4978983.410862541]]]}
geom2_json = {'spatialReference': {'latestWkid': 3857, 'wkid': 102100}, 'rings': [[[-8232619.086036522, 4978994.876241834], [-8232275.11940924, 4979644.590982256], [-8231988.480553171, 4979482.162297151], [-8232380.220323131, 4978822.892928192], [-8232619.086036522, 4978994.876241834]]]}
In [13]:
geom1 = Polygon(geom1_json)
In [14]:
geom2 = Polygon(geom2_json)
In [15]:
geom1
Out[15]:
In [16]:
geom2
Out[16]:
In [17]:
geom_union = geom1.union(geom2)
geom_union
Out[17]:

a2. Difference

The, difference(second_geometry), constructs the geometry that is composed only of the region unique to the base geometry but not part of the second geometry. The following illustration shows the results that are inside the source geometry, but not the second geometry.

In [18]:
geom3_json = {'spatialReference': {'latestWkid': 3857, 'wkid': 102100}, 'rings': [[[-8233020.380435019, 4978303.121194171], [-8232303.783294846, 4979640.769189159], [-8232026.699067313, 4979497.449761124], [-8232791.069350163, 4978112.028623458], [-8233020.380435019, 4978303.121194171]]]}
In [21]:
geom3 = Polygon(geom3_json)
geom3
Out[21]:
In [22]:
geom_diff = geom_union.difference(geom3)
geom_diff
Out[22]:

a3. Symmetric difference

Similar to difference(), the method symmetric_difference(second_geometry) constructs the geometry that is the union of two geometries minus the intersection of those geometries. As told by the definition, the result of difference() should always be included/contained in the result of a symmetric_difference().

In [23]:
geom_sdiff = geom_union.symmetric_difference(geom3)
geom_sdiff
Out[23]:

a4. intersect() V.S. overlaps()

The intersect() constructs a geometry that is the geometric intersection of the two input geometries. Different dimension values can be used to create different shape types. The intersection of two geometries of the same shape type is a geometry containing only the regions of overlap between the original geometries, and its arguments include:

  • second_geometry: required arcgis.geometry.Geometry.
  • A second geometry dimension: required Integer. The topological dimension (shape type) of the resulting geometry, which can be:
    • 1: A zero-dimensional geometry (point or multipoint).
    • 2: A one-dimensional geometry (polyline).
    • 4: A two-dimensional geometry (polygon).
In [28]:
# scroll to the top of this notebook where these two geometries are visualized
geom_intersection = geom1.intersect(second_geometry=geom2, dimension=4)
geom_intersection
Out[28]:

While intersect() returns a new geometry object, the overlaps() will return a boolean value which indicates if the intersection of the two geometries has the same shape type as one of the input geometries and is not equivalent to either of the input geometries.

According to the definition, we can tell that overlaps() contains two operations - first, it creates the intersection of first and second geometries; second, it checks if the intersection is None, or it being equivalent to either the first or the second geometry - if so, then overlaps() returns False, or else, returns True.

In [31]:
# True, because the intersection of geom1 and geom2 is partial shape of geom1, and is not
# equivalent to either one
geom1.overlaps(geom2)
Out[31]:
True
In [33]:
# False, because the intersection of geom1 and geom_intersection is geom_intersection, 
# and that is equivalent to the second geometry
geom1.overlaps(geom_intersection)
Out[33]:
False

a5. Equals

equals() compares the source geometry with the second to check if they are of the same shape type and define the same set of points in the plane. This is a 2D comparison only; M and Z values are ignored.

In [38]:
geom1.equals(geom1)
Out[38]:
True
In [35]:
geom_intersection.equals(geom_sdiff)
Out[35]:
False

a6. generalize() V.S. buffer()

Next, you will see the difference between these two spatial operations:

  • generalize(max_offset) - Creates a new simplified geometry using a specified maximum offset tolerance (as shown in Figs 1 and 2). The result of the generalize() operation against a polyline object is a new polyline, while that against a polygon object is a new polygon.
  • buffer(distance) - Constructs a polygon at a specified distance from the geometry. The buffering process merges buffer polygons that overlap. Negative distances greater than one-half the maximum interior width of a polygon result in an empty geometry. Illustration of buffer() operations against different geomtry types can be found in Fig 3, which also shows that results of buffer() are always polygon objects.

Fig 1. Generialize operation against a polyline object (Source: ArcMap Toolbox Help)

Fig 2. Generialize operation against a polygon object (Source: ArcMap Toolset Help)

Fig 3. Buffer operation (Source: ArcGIS Desktop Help)

To illustrate generalization, let us construct a complex polyline object

In [101]:
polyline1 = Polyline({'spatialReference': {'latestWkid': 3857, 'wkid': 102100},
 'paths': [[[-8235190.176786223, 4978434.716352775], [-8235198.537086192, 4978449.048295577],[-8235190.176786223, 4978458.602924112],[-8235172.261857721, 4978455.019938411],[-8235166.290214886, 4978452.631281278], [-8235154.346929218, 4978452.631281278],[-8235145.9866292495, 4978456.214266978],[-8235145.9866292495, 4978465.768895513], [-8235145.9866292495, 4978471.7405383475],[-8235145.9866292495, 4978480.100838316],[-8235148.375286384, 4978484.878152583], [-8235150.763943518, 4978487.266809717],[-8235160.318572052, 4978488.461138284],[-8235165.09588632, 4978489.65546685], [-8235171.067529154, 4978492.044123984],[-8235178.233500555, 4978492.044123984],[-8235181.816486255, 4978490.849795417], [-8235185.399471956, 4978487.266809717],[-8235197.342757625, 4978478.906509749],[-8235206.89738616, 4978480.100838316],[-8235210.48037186, 4978495.627109685], [-8235210.48037186, 4978508.76472392], [-8235210.48037186, 4978509.959052487], [-8235210.48037186, 4978514.736366754], [-8235210.48037186, 4978520.7080095885], [-8235210.48037186, 4978521.902338156], [-8235210.48037186, 4978523.096666723], [-8235210.48037186, 4978524.29099529], [-8235210.48037186, 4978525.485323856], [-8235210.48037186, 4978529.068309557], [-8235206.89738616, 4978535.039952391], [-8235206.89738616, 4978536.234280958], [-8235205.703057593, 4978542.205923792], [-8235204.508729026, 4978543.400252359], [-8235203.314400459, 4978544.594580926], [-8235202.120071892, 4978545.788909492], [-8235196.148429058, 4978550.56622376], [-8235196.148429058, 4978552.954880893], [-8235196.148429058, 4978554.14920946], [-8235196.148429058, 4978555.343538027], [-8235196.148429058, 4978556.537866594], [-8235194.954100491, 4978557.732195161], [-8235194.954100491, 4978558.926523728], [-8235194.954100491, 4978560.120852294], [-8235193.759771924, 4978561.315180861], [-8235190.176786223, 4978569.67548083], [-8235165.09588632, 4978581.618766498], [-8235165.09588632, 4978592.3677236], [-8235161.512900619, 4978594.756380733], [-8235161.512900619, 4978595.9507093], [-8235156.735586352, 4978599.533695001], [-8235151.958272084, 4978600.728023568], [-8235150.763943518, 4978600.728023568], [-8235147.180957817, 4978603.116680701], [-8235135.237672148, 4978607.893994969], [-8235134.043343581, 4978607.893994969], [-8235132.849015014, 4978607.893994969], [-8235132.849015014, 4978609.088323535], [-8235131.654686447, 4978609.088323535], [-8235130.46035788, 4978609.088323535], [-8235130.46035788, 4978610.282652102], [-8235125.683043613, 4978611.476980669], [-8235105.379457977, 4978611.476980669], [-8235101.796472277, 4978611.476980669], [-8235100.60214371, 4978611.476980669], [-8235099.407815143, 4978611.476980669], [-8235098.2134865755, 4978611.476980669], [-8235097.0191580085, 4978611.476980669], [-8235095.8248294415, 4978611.476980669], [-8235087.464529474, 4978611.476980669], [-8235086.270200907, 4978611.476980669], [-8235085.07587234, 4978611.476980669], [-8235083.881543773, 4978611.476980669], [-8235082.687215206, 4978611.476980669], [-8235081.492886639, 4978611.476980669], [-8235075.521243805, 4978611.476980669], [-8235074.326915238, 4978611.476980669], [-8235063.577958137, 4978607.893994969], [-8235059.994972437, 4978605.505337835], [-8235051.6346724685, 4978591.173395033], [-8235051.6346724685, 4978589.979066466], [-8235050.4403439015, 4978587.590409333], [-8235034.914072532, 4978579.230109365], [-8235027.748101131, 4978558.926523728], [-8235026.553772564, 4978558.926523728], [-8235026.553772564, 4978555.343538027], [-8235028.942429698, 4978555.343538027], [-8235031.331086832, 4978555.343538027], [-8235045.663029634, 4978555.343538027], [-8235051.6346724685, 4978555.343538027], [-8235052.829001036, 4978544.594580926], [-8235052.829001036, 4978543.400252359], [-8235052.829001036, 4978531.45696669], [-8235052.829001036, 4978530.262638124], [-8235052.829001036, 4978526.679652423], [-8235052.829001036, 4978520.7080095885], [-8235049.2460153345, 4978520.7080095885], [-8235033.719743965, 4978520.7080095885], [-8235021.776458297, 4978520.7080095885], [-8235021.776458297, 4978521.902338156], [-8235021.776458297, 4978531.45696669], [-8235016.99914403, 4978537.428609525], [-8235012.221829762, 4978537.428609525], [-8235006.250186928, 4978537.428609525], [-8234994.306901259, 4978530.262638124], [-8234993.112572692, 4978529.068309557], [-8234991.918244125, 4978527.87398099], [-8234991.918244125, 4978526.679652423], [-8234989.529586992, 4978523.096666723], [-8234981.169287024, 4978514.736366754], [-8234981.169287024, 4978513.542038187], [-8234972.808987056, 4978499.210095385], [-8234979.974958457, 4978503.987409652], [-8234962.060029954, 4978489.65546685], [-8234960.865701388, 4978475.323524048], [-8234958.477044254, 4978470.5462097805], [-8234958.477044254, 4978465.768895513], [-8234958.477044254, 4978464.574566946], [-8234958.477044254, 4978463.380238379], [-8234958.477044254, 4978462.185909812], [-8234958.477044254, 4978452.631281278], [-8234958.477044254, 4978437.105009909], [-8234958.477044254, 4978435.910681342], [-8234964.448687088, 4978421.5787385395], [-8234965.643015655, 4978421.5787385395], [-8234977.586301323, 4978408.441124304], [-8234985.946601291, 4978402.46948147], [-8234988.335258425, 4978401.275152903], [-8234988.335258425, 4978400.080824336], [-8234989.529586992, 4978400.080824336], [-8234989.529586992, 4978398.886495769], [-8234996.695558393, 4978394.109181502], [-8234997.889886959, 4978394.109181502], [-8234999.0842155265, 4978394.109181502], [-8235001.4728726605, 4978390.526195802], [-8235008.638844062, 4978382.165895834], [-8235009.833172629, 4978382.165895834], [-8235024.165115431, 4978371.416938731], [-8235036.108401099, 4978367.833953031], [-8235038.497058233, 4978367.833953031], [-8235040.885715366, 4978367.833953031], [-8235054.023329602, 4978364.25096733], [-8235055.217658169, 4978355.890667362], [-8235055.217658169, 4978353.502010229], [-8235055.217658169, 4978352.307681662], [-8235055.217658169, 4978351.113353095], [-8235052.829001036, 4978343.947381694], [-8235040.885715366, 4978320.060810357], [-8235033.719743965, 4978318.86648179], [-8235027.748101131, 4978316.477824656], [-8235018.193472596, 4978312.894838956], [-8235016.99914403, 4978311.700510389], [-8234999.0842155265, 4978311.700510389], [-8234964.448687088, 4978323.643796057], [-8234960.865701388, 4978328.421110325], [-8234960.865701388, 4978329.615438892], [-8234956.08838712, 4978337.97573886], [-8234954.894058553, 4978339.170067427], [-8234954.894058553, 4978340.364395994], [-8234953.6997299865, 4978341.55872456], [-8234953.6997299865, 4978342.753053127], [-8234953.6997299865, 4978343.947381694], [-8234950.116744285, 4978348.724695961], [-8234944.145101451, 4978355.890667362], [-8234942.950772884, 4978355.890667362], [-8234942.950772884, 4978357.084995929], [-8234941.756444317, 4978358.279324496], [-8234941.756444317, 4978359.473653063], [-8234940.56211575, 4978360.66798163], [-8234922.647187248, 4978378.582910133]]]})
polyline1
Out[101]:
In [95]:
from IPython.display import display_svg, clear_output, display, HTML
import time

In the cell below, we loop through various offset distances and observe how that effects the amount of generalization. The higher the offset, the greater the generalization. The greater the generalization, the fewer the vertices, and as such, lesser is the complexity of the resulting geometry. Compact geometries take up less space (for storage) and memory as well.

In [100]:
time.sleep(2)
for offset in [0,10,15,20,25,30,35,40,45,50]:
    # apply generalization
    generalized_polyline = polyline1.generalize(max_offset=offset)
    
    # display logic to create an animation
    display(HTML(f'<p>Offset: {offset}</p>'))
    display_svg(generalized_polyline)
    time.sleep(1)
    clear_output()

Buffer In the cell below, we loop through various distance measures for the buffer tool. We start with a distance of 1m (the unit is meters as that is the units of the geometry's spatial reference), where the buffer result is quite similar to the source geometry. As the distance increases, the effect of buffer increases, producing thicker polygons. The tool generalizes the resulting buffer polygon to produce a smooth shape and dissolves overlaps. At a distance of 70m, the buffer looks like a simple circle.

In [109]:
time.sleep(2)
for distance in [1,10,15,20,25,30,35,40,45,50,55,60,65,70]:
    # apply buffer
    buffered_polyline = polyline1.buffer(distance=distance)
    
    # display logic to create an animation
    display(HTML(f'<p>Buffer distance: {distance}</p>'))
    display_svg(buffered_polyline)
    time.sleep(1)
    clear_output()