ArcMAP 10.4 - Multiple ring Buffer (0.5 m)- Arcpy

4325
19
01-01-2017 08:15 PM
AmilaThilanka
New Contributor II

Hello,

I want to create multiple ring buffer starting with 0.5 m, 1 m, 1.5 m, 2 m,..... up to 500 m around point object. I try to do it by Arcpy - 'MultipleRingBuffer_analysis', unfortunately, it takes very long time and end up with displaying an error.

(But I try it 10 m buffer interval 10 m, 20 m, 30 m, .....500 m, it nicely works)

Does anyone have experience of creating a large number of buffers around single point object....?

Since I'm working with Python (Arcpy), It could be the great help for me to have suggestions in python platform.

Thank you very much for kind attention.

19 Replies
JoshuaBixby
MVP Esteemed Contributor

I can't say I understand your approach, but I can say it is technically possible to get what you are after.  Although ArcPy's Geometry classes don't do a good job of supporting arcs and curves, one can work with arcs and curves indirectly.  For situations such as this, you definitely want to be working with arcs and curves and not approximated arcs and curves.

Below is some code that builds an Esri JSON-compliant Python dictionary containing either circle or ring buffers:

def concentricBuffers(point_geometry, distance, interval, rings=False):
    x = point_geometry.firstPoint.X
    y = point_geometry.firstPoint.Y
    WKID = point_geometry.spatialReference.PCSCode
    
    esri_json = {
      "displayFieldName" : "",
      "geometryType" : "esriGeometryPolygon",
      "spatialReference" : {
        "wkid" : WKID
      },
      "fields" : [
        {
          "name" : "OUTER_DIST",
          "type" : "esriFieldTypeDouble"
        },{
          "name" : "INNER_DIST",
          "type" : "esriFieldTypeDouble"
        }
      ],
      "features" : [
      ]
    }
    
    steps = range(int(distance/interval))
    prev_step = 0
    for step in steps:
        step = (step + 1) * interval
        
        esri_feature = {
          "attributes" : {
            "OUTER_DIST" : step,
            "INNER_DIST" : 0
          },
          "geometry" : {
            "curveRings": [
              [
                [x, y+step],
                {"a": [[x, y+step], [x, y], 0, 1]}
              ]
            ]
          }
        }
        
        if rings:
            esri_feature["attributes"]["INNER_DIST"] = prev_step
            esri_feature["geometry"]["curveRings"].append(
              [
                [x, y+prev_step],
                {"a": [[x, y+prev_step], [x, y], 0, 0]}                
              ]
            )
            
        esri_json["features"].append(esri_feature)
        prev_step = step
    esri_json["features"].reverse()
    return esri_json‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

The code takes an ArcPy PointGeometry, a maximum distance to buffer out, an interval for buffering, and a Boolean indicating circles or rings.  The default is Circles, so make sure to set True for rings.  The Geometry objects section of the ArcGIS REST API explains why I structured the Python dictionary the way I did.

NOTE:  This is designed for projected data, not geographic coordinate system data.  Adding geographic support can be done, but it requires additional code.

The code returns a Python dictionary, which can be passed to ArcPy's AsShape to return a RecordSet that can be exported to a feature class using Copy Features.

>>> pt = arcpy.FromWKT('POINT(10 10)',arcpy.SpatialReference(3857))
>>> arcpy.CopyFeatures_management(pt,'in_memory/pt')
<Result 'in_memory\\pt'>
>>> rings = concentricBuffers(pt, 10, 3, True)
>>> arcpy.CopyFeatures_management(arcpy.AsShape(rings, True), "in_memory/rings")
<Result 'in_memory\\rings'>‍‍‍‍‍‍

The code can generate thousands of rings in a second or less, and it takes a second or two to dump a 1,000 ring record set to disk.

DanPatterson_Retired
MVP Emeritus
>>> top = _arc(radius=10, start=0, stop=1, step=0.2, xc=0.0, yc=0.0)
>>> top.reverse()
>>> bott = _arc(radius=9.5, start=0, stop=1, step=0.2, xc=0.0, yc=0.0)
>>> top = np.array(top)
>>> bott = np.array(bott)
>>> close = top[0]
>>> arc = np.asarray([i for i in [*top, *bott, close]])
>>> pnts = arc.tolist()
>>> pnts
[[9.99902524, 0.1396218], [9.99945169, 0.10471784], [9.99975631, 0.0698126],
 [9.99993908, 0.03490651], [10.0, 0.0], [9.5, 0.0], [9.49994212, 0.03316119],
 [9.49976849, 0.06632197], [9.49947911, 0.09948195], [9.49907398, 0.13264071],
 [9.99902524, 0.1396218]]

If someone wants to check that this forms a valid polygon (on the iThing, so I can't check)

I have some code that performs arc calculations.


If it does... or needs fixing,

It is designed initially to create the cirular arc from a circle radius, a start and stop angle, then a densification step, centered about an origin (xc, yc).  In this example, I just calculated the top and bottom segments. tried to make sure that the arc formed a closed circular arc.

The code is

def _arc(radius=100, start=0, stop=1, step=0.1, xc=0.0, yc=0.0):
    """
    :Requires:
    :---------
    :  radius = cirle radius from which the arc is obtained
    :  start, stop, incr = angles in degrees
    :  xc, yc - center coordinates in projected units
    :  
    :Returns: 
    :--------
    :  points on the arc
    """
    start, stop = sorted([start, stop])
    angle = np.deg2rad(np.arange(start, stop, step))
    Xs = radius*np.cos(angle)         # X values
    Ys = radius*np.sin(angle)         # Y values
    pnts = np.array(list(zip(Xs, Ys)))
    pnts = np.round(pnts + [xc, yc], 8)
    p_lst = pnts.tolist()
    return p_lst

You can then generate each sector singly to do the sampling .... I would NOT recommend making a sampling framework in this manner...

DanPatterson_Retired
MVP Emeritus

here is what the sector are of 1/2 degree with a 50 cm separation looks like between 9.5 and 10 meters out from a point.  notice, that the circular arc points are not even noticeable, but they are there.  You would be better off simplifying the whole analysis using a fishnet or using the code as a sampling tool

here is what 5 degrees looks like

DanPatterson_Retired
MVP Emeritus

Well it can be done, by using the arc code and cycling through inner and outer radii.

If that is what you want... the example is for 5 degree sector widths and each sectors points are densified.

I cut out the buffer from 0 to 5 m since the points would be too dense.

Here is 1/4 of a circle with 1 meter buffer/sector widths by 1 degree 

As you can see, the sectors are quite thin  and I suspect sampling your raster will have several sectors recording the same values  

Perhaps you can re-examine the sizes you want in both the x and y direction since you will notice that the sample space changes with the angle, preferring a different axis as the sectors get rotated.

DanPatterson_Retired
MVP Emeritus

of course if you generate your own multiring buffer code, you can vary the density of the points used to create the rings, effective, moving from a dense ngon (360+ points) down to triangles.

Or add in a rotation and scaling factor to the circle and generate ellipse rings.

curtvprice
MVP Esteemed Contributor

Dan, can you post the code you used for these? Cool!

0 Kudos
DanPatterson_Retired
MVP Emeritus

I'll throw it in a blog and github it if that is ok... just trying to get some stuff ready for classes before next week... so maybe tonight...

curtvprice
MVP Esteemed Contributor

Dan, you're a maniac. My classes start next week too and have no idea how you do it all!

0 Kudos
DanPatterson_Retired
MVP Emeritus

Sleep is inconvenient and repetitive  ... GitHub link plus the images will appear on my blog ... tomorrow 10:00 am Ottawa, Canada...  Take a look at some of the  other geometry stuff... like the Julia set demo files... It's how I am introducing programming... cool before the cursors...