Select to view content in your preferred language

Constrained triangulation of polygons

57
0
yesterday
Labels (1)
DanPatterson
MVP Esteemed Contributor
0 0 57

Triangulating geometry was a "thing" at one time.

It can be done if you have the 3D Analyst extension, but if you don't and you need to know the principles and can work NumPy and python... here goes.

1 make an array out of the geometry (FeatureClassToNumPyArray is a starter.  I have posted code before on how to do this
2 triangulate it  (I use scipy)
3 get the centroid of each triangle. (again, I have posted code for this.)
4 check if the centroids are within the original shape.

Now to complicate this, polygons are either convex or concave, with or without holes and they can consist of more than one part.  So sometimes, you need to devolve your geometry to its simplest form and cycle through.  Convex shapes are boring, so I will ignore them.

Here are 3 shapes, C, D, A.  C is a concave shape with no holes.  D is convex but it has a hole.  A is concave and has a hole.

C.pngD0.pngA0.png

 

 

 

 

 

 

Their triangulated versions.

 5f2d4ccd-904f-4aa5-aaf1-4334ecbfeeb4.pngD0_triangulation.pngA0_triangulation.png

 

 

 

 

And the constrained triangulation.  That is, the triangulation with the triangles not in the original outer hull or in a hole.

C_constrained_triangulation.pngD0_constrained_triangulation.pngA0_constrained_triangulation.png

Looks like all is good.

To perform the triangulation, you can use

from scipy.spatial import Delaunay

def triangulate_pnts(pnts):
    """Triangulate the points and return the triangles.

    Parameters
    ----------
    pnts : array
        Points for a shape or a group of points in array format.
        Either geo.shapes or np.ndarray.
    out : array
        An array of triangle points.

    .. note::
       The simplices are ordered counterclockwise, this is reversed in this
       implementation.
    """
    pnts = np.unique(pnts, axis=0)    # get the unique points only
    avg = np.mean(pnts, axis=0)
    p = pnts - avg
    tri = Delaunay(p)
    simps = tri.simplices
    # -- indices holder, fill with indices, repeat first and roll CL
    # translate the points back
    z = np.zeros((len(simps), 4), dtype='int32')
    z[:, :3] = simps
    z[:, 3] = simps[:, 0]
    tmp_ = p[z] + avg
    new_pnts= []
    for i in tmp_:  # reorder clockwise
        if _bit_area_(i) < 0.0:  # -- 2025_10_27
            i = i[::-1]
        new_pnts.append(i)                    
    return new_pnts

def _bit_area_(a):
    """Mini e_area, used by `areas` and `centroids`.

    Negative areas are holes.  This is intentionally reversed from
    the `shoelace` formula.
    """
    a = _base_(a)
    x0, y1 = (a.T)[:, 1:]  # cross set up as follows
    x1, y0 = (a.T)[:, :-1]
    e0 = np.einsum('...i,...i->...i', x0, y0)  # 2024-03-28 modified
    e1 = np.einsum('...i,...i->...i', x1, y1)
    return np.sum((e0 - e1) * 0.5)

def _area_centroid_(a):
    r"""Calculate area and centroid for a singlepart polygon, `a`.

    This is also used to calculate area and centroid for a Geo array's parts.

    Notes
    -----
    For multipart shapes, just use this syntax:

    >>> # rectangle with hole
    >>> a0 = np.array([[[0., 0.], [0., 10.], [10., 10.], [10., 0.], [0., 0.]],
                      [[2., 2.], [8., 2.], [8., 8.], [2., 8.], [2., 2.]]])
    >>> [_area_centroid_(i) for i in a0]
    >>> [(100.0, array([ 5.00,  5.00])), (-36.0, array([ 5.00,  5.00]))]
    """
    a = _base_(a)
    x0, y1 = (a.T)[:, 1:]
    x1, y0 = (a.T)[:, :-1]
    e0 = np.einsum('...i,...i->...i', x0, y0)
    e1 = np.einsum('...i,...i->...i', x1, y1)
    t = e1 - e0
    area = np.sum((e0 - e1) * 0.5)
    x_c = np.sum((x1 + x0) * t, axis=0) / (area * 6.0)
    y_c = np.sum((y1 + y0) * t, axis=0) / (area * 6.0)
    return area, np.asarray([-x_c, -y_c])

Here is C

C
array([[  0.00,   0.00],
       [  0.00,  10.00],
       [ 10.00,  10.00],
       [ 10.00,   8.00],
       [  2.00,   8.00],
       [  2.00,   2.00],
       [ 10.00,   2.00],
       [ 10.00,   0.00],
       [  0.00,   0.00]])

The centroids and areas and triangulations can be determined using the above.

Testing which triangles are part of the original geometry entails using a points-in-polygon search.  I did this way back in my previous incarnation.

Point in Polygon ... Geometry Mysteries - Esri Community

It uses the winding number approach.

So if you have a need for a constrained Delaunay triangulation of your geometry objects... give it a try.

Note:

If you want to see more python, NumPy and ArcPy stuff see my github site.

Dan Patterson on github

 

 

Tags (3)
Contributors
About the Author
Retired Geomatics Instructor (also DanPatterson_Retired). Currently working on geometry projects (various) as they relate to GIS and spatial analysis. I use NumPy, python and kin and interface with ArcGIS Pro.
Labels