Select to view content in your preferred language

Geometry ... Deconstructing poly* features # 3

561
0
04-10-2019 04:24 PM
Labels (1)
DanPatterson_Retired
MVP Emeritus
0 0 561

Part 3... Alternate geometry deconstructors

---- On to the examples ----

The polygons that I will be using are shown to the right.

  1. A square, 5 points, first and last duplicates
  2. Donut with a Timbit inside
  3. A multipart with a donut hole in each
  4. The letter 'C'
  5. The letter 'V'

Each part is labelled at the labelpoint rather than the centroid, hence each part gets labelled.

 

I will be omitting examples that rely on json representation or the __geo_interface__ method since they don't add much to the functionality of constructing and deconstructing poly* type features.

---- An Alternate Geometry Deconstructor ----

---- Poly* Features to Points ----

This one may seem a bit odd, so I will explain it abit.Returns

  •  Line 13, gets the spatial reference for the featureclass
  • A null point is created on line 16... none of this None stuff or a 
    • not_null_point = arcpy.Point()
      not_null_point.X   #  0.0
      not_null_point.Y   #  0.0
  • Line 17, your familiar SearchCursor is used to pull the geometry object out for further work. (I use an enumerator since I use 'i' in a different incarnation of the script...more in a different blog)
  • Line 21 pulls our arcpy Point coordinates unless 'None' is found.  If it is, then it is replaced with my null_pnt.
  • Line 24 records the number of points in the shape.  This value is used later for poly* reconstruction and is returned is 'as_obj' is False.
  • Line 26 produces a numpy array from the coordinates and null_pnts.
  • Line 27 does a cumulative sum of the points in each shape.  This is a key... 'c' allows us to split a 2D array of points into subgroups.  A subgroup may contain a null_pnt which further allows one to split the points into parts whether they be interior rings and/or array parts.
  • And the final lines.  If 'as_obj' is True a list of arrays is returned.  The arrays may be conventional 2D arrays, 3D arrays or object arrays.  Not for you to worry though.  If 'as_obj' is false, then a 2D list of points is returned as well as the 'c' array should you need to split it later.

Why the difference

  • With the 2D array of points, it is blooming easy to determine the parameters for the whole array dataset multiple times without having to repeat things.  For example, means, std deviations.
  • The 2D array allows you to do things that you can't do easily.  Every tried to rotate polygons about an origin point?  How about a tiny shift in the coordinates.  A quick densification but you only have a Standard license?  Think of something that you can't do easily with the plain arcpy geometry objects... they are easy in array format.
  • With the object array option, you can pick and choose which geometry objects to operate on and you don't need to reconstruct them prior to sending them back to arcpy geometries.

====================================================================================

The Script

def poly_pnts(in_fc, as_obj=False):
    """Poly features to points while maintaining the location of the points in
    the input features. null points are replaces with their numpy equivalent.

    Parameters
    ----------
    in_fc : text
        Full path to the featureclass.
    as_obj : boolean
        True returns a list of arrays of the shapes.  The array types may be
        mixed.  False, returns a 2D array of points and an array of indices.
    """
    SR = getSR(in_fc)    
    out = []
    c = []
    null_pnt = (np.nan, np.nan)  #  inf_pnt = [np.PINF, np.PINF]
    with arcpy.da.SearchCursor(in_fc, 'SHAPE@', spatial_reference=SR) as cur:
        for i, row in enumerate(cur):
            sub = []
            poly = row[0]
            pnts = [[pt.X, pt.Y] if pt else null_pnt
                     for arr in poly for pt in arr]
            sub.extend(pnts)  # removed from next line  sub.append(inf_pnt)
            c.append(len(pnts))
            out.extend(sub)
    out = np.asarray(out)
    c = np.cumsum(c)
    if as_obj:
        return np.split(out, c)[:-1]  # cut off the last empty slice
    return out, c‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

---- Results ....

a, c = poly_pnts(in_fc, as_obj=False)

a.shape  # (58, 2) ... 58 points of X,Y coordinates

a
array([[ 300010., 5000000.],
       [ 300000., 5000000.],
       [ 300000., 5000010.],
       [ 300010., 5000010.],
       [ 300010., 5000000.],  # ---- 5th point, last of the first polygon
       [ 300010., 5000020.],
       [ 300010., 5000010.],
       [ 300000., 5000010.],
       [ 300000., 5000020.],
       [ 300010., 5000020.],
       [     nan,      nan],  # ---- end of the first part of the second shape
    .... big snip

c
array([ 5, 21, 41, 50, 58], dtype=int32)  # note '5' ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

--------------------------------------------------------------------------

Coming soon

The next blog post will go over reconstructing the geometries after processing their array representation.

Other links

/blogs/dan_patterson/2019/03/17/geometry-in-numpy-1 

/blogs/dan_patterson/2019/04/10/geometry-arcpy-and-numpy-2 

About the Author
Retired Geomatics Instructor at Carleton University. I am a forum MVP and Moderator. Current interests focus on python-based integration in GIS. See... Py... blog, my GeoNet blog...
Labels