In step 4, you have to change the CRS from NAD83 to another projection that uses metres as units.
It depends on the extent of your data which one is best. Unfortunately, your data is located all over the world, so you could:
- Create a custom CRS using aeqd (or tmerc) for each one, and draw just that one buffer with it. Practically, you only have to create the buffer once, and exchange the CRS information in the .prj and .qpj file. The coordinates of the buffer with respect to its center will always be the same.
- Group the data according to the UTM zones, and use the UTM CRS of that zone for those points.
- Similar to UTM, group your points into zones of latitude (e.g. every 10 degrees), and create custom Lambert conformal conical 2SP CRS for each group. This will be significantly faster than using all northern and southern UTM zones of the world.
- Use pseudo mercator EPSG:3857 for all. The buffers will look like nice circles, but the real size will get smaller and distorted the more to the poles you come.
Input:
Output:
Algorithm:
- Extend line on both ends by certain amount and do flat ends buffer
- Repeat by changing extension length until precision required met
Script:
import arcpy
from arcpy import env
env.overwriteOutput = True
singleLine=r"in_memory\line"
singleBuffer=r"in_memory\pgon"
mxd = arcpy.mapping.MapDocument("CURRENT")
lines = arcpy.mapping.ListLayers(mxd,"LINES")[0]
buffers = arcpy.mapping.ListLayers(mxd,"BUFFERS")[0]
curT=arcpy.da.InsertCursor(buffers,"SHAPE@")
with arcpy.da.SearchCursor(lines,["SHAPE@","AREA_M2"]) as cursor:
for shp,target in cursor:
part=shp.getPart(0);origList=list(part)
## get last vertices vector
p1,p2=origList[-2],origList[-1]
dX=p2.X-p1.X; dY=p2.Y-p1.Y; d1=pow(dX*dX+dY*dY,0.5)
dXend=dX/d1;dYend=dY/d1
## get first vertices vector
p1,p2=origList[1],origList[0]
dX=p2.X-p1.X; dY=p2.Y-p1.Y; d2=pow(dX*dX+dY*dY,0.5)
dXstart=dX/d2;dYstart=dY/d2
## define bounds
L=target/shp.length/2
low=L/2;high=L*2
while True:
middle=0.5*(low+high)
## extend line
p1=arcpy.Point(origList[-1].X+middle*dXend,origList[-1].Y+middle*dYend)
p2=arcpy.Point(origList[0].X+middle*dXstart,origList[0].Y+middle*dYstart)
extList=[p2]+origList+[p1]
extLine=arcpy.Polyline(arcpy.Array(extList))
arcpy.CopyFeatures_management(extLine, singleLine)
## get buffer ind repeat
arcpy.Buffer_analysis(singleLine, singleBuffer, middle,"FULL","FLAT","NONE","","PLANAR")
with arcpy.da.SearchCursor(singleBuffer,"SHAPE@") as inMem:
for r in inMem:
pgon=r[0]
# tolerance
if (high-low)<0.01: break
curArea=pgon.area
if curArea<target:low=middle
else:high=middle
arcpy.AddMessage("Difference {:8.2f}%".format((curArea-target)/target*100))
curT.insertRow((pgon,))
arcpy.Delete_management("in_memory")
It is good you are proficient with arcpy, you'll figure out parameters
UPDATE:
Your approach gives the impression of working simply because your lines are not that bendy and most importantly buffers are skinny. Try with the shape below and buffering distance comparable to line length and you’ll notice significant increase in mismatch with target area.
The class of problems you are dealing with has no analytical solution. However an accurate estimate can be found numerically through iterations, tries and errors but much quicker by using one of well known root-finding techniques. I used the method of bisection that is not very efficient. The golden section or others might converge faster. Nevertheless in took only 18 iterations to find buffer distance, accurate to 0.01 m for 12 km long shape with 1000 vertices shown above. As for it’s accuracy, gauge by yourself, I am struggling to express it using percents.
It took 5 seconds only on my out of date machine at home. Unfortunately buffer() method of arcpy geometry does not support flat ends ( this is why I had to use tools working in_memory), otherwise it could be done in no time.
Best Answer
Menu Processing > Toolbox > Geometry by expression
with this expression:Menu Vector > Geometry tools > Multipart to singleparts
:Remark: Line 3 in the above expression contains the distances for the bufers: 30 is the initial distance for the first buffer; 39 is the distance of the last buffer (from 30 to 39, you get 10 elements), 1 is the distance between each buffer (here, for the value of
1
, this is optional).The solution, here demonstrated with Geometry Generator: