3

I'm trying to learn about ArcPy's geometry class.

From my limited experience, it seems like there is a hierarchical nature to the components of a geometry:

import arcpy
connection = "Database Connections\my_conn.sde"
feature_class = connection + "\my_owner.my_fc"

with arcpy.da.SearchCursor(feature_class, ["SHAPE@","OID@"]) as cursor:
    for row in cursor:
        geometry = row[0]
        print("Geometry/Polyline: {}".format(row[1]))
        for i, part in enumerate(geometry):
            print("  Part: {}".format(i))
            for j, point in enumerate(part):
                print("    Point: {}".format(j))

>>> 
Geometry/Polyline: 100
  Part: 0
    Point: 0
    Point: 1
    Point: 2
    Point: 3
    Point: 4
  Part: 1
    Point: 0
    Point: 1

I want to learn more about the levels in the Geometry class' hierarchical structure.

From looking at the Geometry Class docs, I'm wondering if the classes/levels would be organized like so:

 - Geometry
    - PointGeometry 
        - Point
    - Multipoint
        - Point
    - Polygon
        - Ring (aka part)
           - Point
    - Polyline
        - Path (aka part)
           - Point

Question:

Is that how the hierarchical levels in the Geometry class are structured?


It would be nice if there were a hierarchical diagram in the docs, kind of like we have for the SDE.ST_GEOMETRY spatial type. But I haven't found anything like that yet.

4

4 Answers 4

8
+50

Class Hierarchy

That's a reasonable assumption, but the class hierarchy for the ArcPy Geometry objects is more like this:

  • Geometry
    • PointGeometry
    • Multipoint
    • Polyline
    • Polygon

You can say that "Multipoint is a kind of Geometry".

So where does Point come in? Each of these classes store their coordinates in one or more Point objects, but they do not inherit from it.

So PointGeometry is not a kind of Point, and Point is not a kind of PointGeometry. But PointGeometry contains a Point. It also contains a SpatialReference object, defining the Coordinate Reference System that the point is in.

Meanwhile, Multipoint, Polyline and Polygon each contain multiple Point objects.

It's worth reading up on some of the formal theory of Object-Oriented Programming here, and the distinction between inheritance and encapsulation.

Trying out points

You can learn a lot by opening a Python window, constructing some of these objects, and querying their properties.

If you want to specify real-world geometry, you also need a SpatialReference.

Edited to add: Vince in the comments warns that all geometry should have a SpatialReference, to avoid your coordinates being rounded down.

Here's how you construct a PointGeometry object:

spatialReferenceWGS1984 = arcpy.SpatialReference("WGS 1984")
pointZero = arcpy.Point(0,0)
pointGeometryZero = arcpy.PointGeometry(pointZero, spatialReferenceWGS1984)

How do you then get the contents out again? All of these:

pointGeometryZero.firstPoint
pointGeometryZero.lastPoint
pointGeometryZero.centroid
pointGeometryZero[0]

Will return:

<Point (0.0, 0.0, #, #)>

The constructor for Point takes the X coordinate, then the Y coordinate. If using degree coordinates, this means longitude, then latitude. Or use keyword arguments to make it unambiguous, which I have done here.

spatialReferenceWGS1984 = arcpy.SpatialReference("WGS 1984")
pointTokyoTower = arcpy.Point(X=139.745556, Y=35.658611)
pointGeometryTokyoTower = arcpy.PointGeometry(pointTokyTower, spatialReferenceWGS1984)

Trying out Polyline

Let's build this shape:

Lines arranged in squared-off C shapes

I tried to do this:

outerRingPolyline = arcpy.Polyline([arcpy.Point(0,0), arcpy.Point(4,0), arcpy.Point(4,3), arcpy.Point(0,3)], spatialReferenceWGS1984)

Which returned this error:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "C:\Program Files\ArcGIS\Pro\Resources\ArcPy\arcpy\arcobjects\geometries.py", line 218, in __init__
    super().__init__(inputs, spatial_reference, has_z, has_m)
  File "C:\Program Files\ArcGIS\Pro\Resources\ArcPy\arcpy\arcobjects\mixins.py", line 224, in __init__
    *gp_fixargs(args, True))
  File "C:\Program Files\ArcGIS\Pro\Resources\ArcPy\arcpy\geoprocessing\_base.py", line 512, in <lambda>
    return lambda *args: val(*gp_fixargs(args, True))
RuntimeError: Object: CreateObject cannot create geometry from inputs

This happened because ArcPy requires that multiple points must by inside the ArcPy Array Object, not just in a Python array.

outerRingPoints = arcpy.Array([arcpy.Point(0,0), arcpy.Point(4,0), arcpy.Point(4,3), arcpy.Point(0,3)])
outerRingPolyline = arcpy.Polyline(outerRingPoints, spatialReferenceWGS1984)

innerRingPoints = arcpy.Array([arcpy.Point(1,1), arcpy.Point(3,1), arcpy.Point(3,2), arcpy.Point(1,2)])
innerRingPolyline = arcpy.Polyline(innerRingPoints, spatialReferenceWGS1984)

bothRingsPoints = arcpy.Array([outerRingPoints, innerRingPoints])
bothRingsPolyline = arcpy.Polyline(bothRingsPoints, spatialReferenceWGS1984)

We can then query this object in various ways:

bothRingsPolyline.partCount
2
bothRingsPolyline.pointCount
8
bothRingsPolyline.length
16.0
# They're lines, so they have no area.
bothRingsPolyline.area
0.0
bothRingsPolyline.boundary()
<Multipoint object at 0x18867be2cc8[0x188681684b0]>
# We can look request lines or points by array reference.
bothRingsPolyline[0]
<Array [<Point (0.0, 0.0, #, #)>, <Point (4.0, 0.0, #, #)>, <Point (4.0, 3.0, #, #)>, <Point (0.0, 3.0, #, #)>]>
bothRingsPolyline[0][0]
<Point (0.0, 0.0, #, #)>
bothRingsPolyline[1][3]
<Point (1.0, 2.0, #, #)>

Trying out Polygon

Try constructing this shape using arcpy.Polygon. It uses the same points as above.

Rectangle clipped out of larger rectangle

Comparing with the Polyline results above, what do you expect the area, partCount and pointCount to be? Try it out and see what happens. Some of the numbers may surprise you, but you should be able to work out why.

Apologies for the long-winded answer, but there are indeed some quirky choices in the implementation of ArcPy that are worth understanding.

1
  • Thank you, Vince. I'm among the people who wasn't aware of that truncation. So is there a SpatialReference to use when constructing abstract geometry that has nothing to do with the Earth's surface, as I've done here?
    – Cowirrie
    Feb 15, 2022 at 21:05
5

To answer the question upfront, no, the ArcPy Geometry class hierarchy is not structured as shown.

In terms of previous answers, I think Dondragmer's is correct, although there are a few items I would like to add.

For the most part ArcPy is a Python wrapper around ArcGIS compiled code, whether that is ArcGIS Desktop (ArcMap, ArcGIS Pro) or ArcGIS Enterprise. You can't have ArcPy without having certain ArcGIS products installed and licensed. If one wants to learn the underlying geometry object model and class hierarchy being used by those products, look to their respective developer documentation. For ArcGIS Desktop:

Although ArcPy is effectively a Python wrapper, it doesn't mean it implements its own code structure identically to the underlying ArcGIS compiled code. For example, ArcPy doesn't have Envelope or GeometryBag although ArcGIS Desktop/ArcMap and ArcGIS Pro do in their compiled code.

For ArcPy, the standard Geometry -- ArcGIS Pro | Documentation is really the developer documentation too. That said, inspecting the ArcPy classes can give you a Geometry class hierarchy of sorts (although I am not sure what value comes from it).

# custom function for printing base class and subclass hierarchies
def print_classtree(cls, bases=True, level=0):
    print(f"{'-'*2*level} {cls}")
    if bases:
        classes = cls.__bases__
    else:
        classes = cls.__subclasses__()
    for cls in classes:
        print_classtree(cls, bases, level + 1)

Exploring ArcPy Polygon:

>>> # look up the Polygon class tree
>>> print_classtree(arcpy.Polygon, True)
 <class 'arcpy.arcobjects.geometries.Polygon'>
-- <class 'arcpy.arcobjects.arcobjects.Geometry'>
---- <class 'arcpy.arcobjects.mixins.GeometrySpecializationMixin'>
------ <class 'object'>
---- <class 'arcpy.arcobjects._base._BaseArcObject'>
------ <class 'object'>
>>> 
>>> # look down the Polygon class tree
>>> print_classtree(arcpy.Polygon, False)
 <class 'arcpy.arcobjects.geometries.Polygon'>
>>> 

One can see Polygon inherits from Geometry which subsequently inherits from GeometrySpecializationMixin and _BaseArcObject. One can also see Polygon is a dead-end in terms of class hierarchy, nothing inherits from it.

If nothing inherits from Polygon, then it raises the question of how Point fits into the hierarchy.

Exploring ArcPy Point:

>>> # look up the Point class tree
>>> print_classtree(arcpy.Point, True)
 <class 'arcpy.arcobjects.arcobjects.Point'>
-- <class 'arcpy.arcobjects.mixins.PointMixin'>
---- <class 'object'>
-- <class 'arcpy.arcobjects._base._BaseArcObject'>
---- <class 'object'>
>>>
>>> # look down the Point class tree
>>> print_classtree(arcpy.Point, False)
 <class 'arcpy.arcobjects.arcobjects.Point'>
>>>

One can see that Point breaks from the Geometry hierarchy at _BaseArcObject, which is near the root of ArcPy structure overall.

I tend to think of Point as an information exchange format and not a fundamental part of ArcPy geometries. The various ArcPy geometry constructors understand what a Point is and can construct a geometry from an Array of them, but the geometries are not fundamentally made up of them under the hood. How exactly a geometry is structured is defined somewhere besides ArcPy and is implemented in ArcGIS compiled code.

Exploring ArcPy Geometry:

Looking at the ArcPy classes, it is interesting to note there are more geometry classes than Esri covers in the documentation.

>>> # look down the Geometry class tree
>>> print_classtree(arcpy.Geometry, False)
 <class 'arcpy.arcobjects.arcobjects.Geometry'>
-- <class 'arcpy.arcobjects.geometries.Annotation'>
-- <class 'arcpy.arcobjects.geometries.Dimension'>
-- <class 'arcpy.arcobjects.geometries.Multipatch'>
-- <class 'arcpy.arcobjects.geometries.Multipoint'>
-- <class 'arcpy.arcobjects.geometries.PointGeometry'>
-- <class 'arcpy.arcobjects.geometries.Polygon'>
-- <class 'arcpy.arcobjects.geometries.Polyline'>
>>>

So how do we construct a Multipatch?

1

The only documentation I'm aware of showing you the relationships of all the different geometry classes is the ArcObjects object module diagram. I don't know how relevant this is to ArcGIS Pro and it's development environment.

0

@Vince mentioned this diagram:

Class Diagram of the ESRI.ArcGIS.Mobile.Geometries namespace

It’s not for ArcPy, but the structure is pretty close to ArcPy’s structure. So it’s still fairly helpful.

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.