# Creating buffers in meters for locations around the world using Python

I have a location dataset (points long & lat) for events that happened around the World. I want to create 5km rings around these locations using `Geopandas.buffer` function. Following in a previous post How to use GeoPandas buffer function to get buffer zones in kilometers?, I first transform to meters and then compute the buffers.

I realize that EPSG:32634 is not appropriate for every country and appears to lead to distortions in the size of these buffers. Do I have to manually pick the "best" projection for each point or is there a way to code this? Or is the transformation to meters actually not needed?

I need to compute some statistics for the area within each of these buffers. I have to make sure that the size of the buffers is the same around the world. I don't need them for visualization purposes.

You can use `.estimate_utm_crs` method to find a suitable utm crs for each point, reproject to it and buffer:

``````import geopandas as gpd
import shapely

buffer_distance = 1000000 #Buffer radius in meters

#Create a point dataframe with 10 points around the world in EPSG:4326 coordinates
wkt_list = ['Point (146.66667606373533772 62.23890379786246285)',
'Point (34.32764669470472541 -41.38552524503352714)',
'Point (49.8860350310458216 -4.91479315765549529)',
'Point (-83.06099993189856434 14.366032762331713)',
'Point (-70.56585431584979062 -16.25292544986456278)',
'Point (-101.91449225955174995 -48.47418794019157673)',
'Point (-122.57824978678837624 -42.82228245839684888)',
'Point (-166.26390599168405515 -32.96525451181832267)',
'Point (-100.36153965087687823 26.92149042530141401)',
'Point (-125.12779657560248836 50.35334977372303911)']
geometries = [shapely.wkt.loads(x) for x in wkt_list]
df = gpd.GeoDataFrame(geometry=geometries, crs=4326)

df["id"] = range(0, df.shape[0]) #Create a unique id for each point/row

buffer_geometries = [] #A list to hold buffered points
for idnum, subframe in df.groupby("id"): #For each row
estimated_utm = subframe.estimate_utm_crs() #Estimate a utm crs
subframe = subframe.to_crs(estimated_utm) #Reproject the point to this
subframe = subframe.buffer(distance=buffer_distance) #Buffer the point
buffer_geometries.append(subframe.to_crs(4326)) #Reproject the resulting series back to 4326 and append to list

new_geoms = gpd.pd.concat(buffer_geometries) #From a list of series to a data frame

result = df.copy()
result["geometry"] = new_geoms

#Plot
world_map = gpd.read_file("https://datahub.io/core/geo-countries/r/countries.geojson") #A world map for plotting (23 MB)
ax = world_map.plot(figsize=(20,10), facecolor="honeydew", edgecolor="black")
result.plot(ax=ax, facecolor="#FFC107", edgecolor="#004D40")
df.plot(ax=ax, color="#D81B60", markersize=80)
``````

EPSG 3857 is a geodetic CRS that is measured in meters. I'd recommend transforming to that projection then perform your buffer and analysis.

Complementing @BERA's response, I took the time to validate the % of the estimate of the named method versus postgres sql postgis I will leave all the data below to validate this data, to my surprise I could say that at least with a 5km buffer the buffers They are practically the same .

Personally I need the buffers to be identical to those provided by Postgres, but because the BERA image does not have 100% circular buffers I have doubts, and I prefer to do this comparison (Maybe it's for some reason due to the type of map projection he used? I don't know I'm not an expert in GIS), I leave it in case anyone else has a similar doubt, and He doesn't know how to solve it.

Despite the above, I created another function to be able to escalate the problem with large massive data in spark and be able to make a udf.I leave the code below.

``````from pyproj import CRS
from pyproj.database import query_utm_crs_info
from pyproj.aoi import AreaOfInterest

def estimate_utm_crs(wkt_point):
try:
# from : https://github.com/geopandas/geopandas/blob/514f975298b940fca1a39917ff35aa12b149a1e7/geopandas/array.py#L998
x_center = geometry.x
y_center = geometry.y
utm_crs_list = query_utm_crs_info(
datum_name="WGS 84",
area_of_interest=AreaOfInterest(
west_lon_degree=x_center,
south_lat_degree=y_center,
east_lon_degree=x_center,
north_lat_degree=y_center,
),
)

return CRS.from_epsg(utm_crs_list[0].code)
except IndexError:
raise RuntimeError("Unable to determine UTM CRS")

wkt = 'POINT(-59.23828125000001 -34.95799531086792)'
epsg_code = estimate_utm_crs(wkt)
print(epsg_code)
``````

For compare buffer im use this page for plot polygons, can plot all polys separated by , inside a GEOMETRYCOLLECTION() : wkt-playground

Table of buffer from postgres sql

``````SELECT ST_Buffer( ST_GeomFromText('POINT(-122.51953124999999 47.75409797968001)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-116.01562499999999 34.161818161230386)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-105.82031249999999 24.846565348219755)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-94.39453124999997 16.636191878397653)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-85.25390624999997 10.660607953624776)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-75.76171874999997 7.188100871179017)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-67.85156249999999 1.0546279422759)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-78.92578124999999 -1.7575368113083272)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-77.16796874999996 -10.48781188205669)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-71.71874999999999 -16.130262012034734)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-67.85156249999999 -22.431340156360605)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-69.873046875 -27.21555620902968)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-70.83984375 -31.353636941500987)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-70.576171875 -35.389049966911664)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-78.74999999999999 -33.431441335575286)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-80.59570312500001 -33.87041555094184)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-72.861328125 -39.77476948529545)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-67.1484375 -35.88905007936091)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-63.544921875 -35.96022296929668)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-59.23828125000001 -34.95799531086792)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-55.89843749999999 -32.472695022061494)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-53.525390625000014 -33.65120829920498)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-58.18359374999999 -37.85750715625202)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-63.017578125 -40.44694705960048)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-68.5546875 -43.19716728250127)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-73.12500000000001 -45.33670190996812)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-67.67578125000003 -46.679594465640186)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-74.8828125 -49.15296965617039)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-69.43359375000001 -49.15296965617039)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-73.82812500000003 -52.16045455774705)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-68.90625000000001 -52.26815737376817)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-68.73046874999999 -55.27911529201561)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-65.390625 -67.57571741708055)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-66.44531250000001 -71.82883956585104)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep union all
SELECT ST_Buffer( ST_GeomFromText('POINT(-72.685546875 -71.60828252210263)')::geography  , 5000  )::geometry(Geometry, 4326)  AS buffered_geometry , ',' as sep
``````

And same points for test in BERA's method

``````wkt_list = [
'POINT(-122.51953124999999 47.75409797968001)',
'POINT(-116.01562499999999 34.161818161230386)',
'POINT(-105.82031249999999 24.846565348219755)',
'POINT(-94.39453124999997 16.636191878397653)',
'POINT(-85.25390624999997 10.660607953624776)',
'POINT(-75.76171874999997 7.188100871179017)',
'POINT(-67.85156249999999 1.0546279422759)',
'POINT(-78.92578124999999 -1.7575368113083272)',
'POINT(-77.16796874999996 -10.48781188205669)',
'POINT(-71.71874999999999 -16.130262012034734)',
'POINT(-67.85156249999999 -22.431340156360605)',
'POINT(-69.873046875 -27.21555620902968)',
'POINT(-70.83984375 -31.353636941500987)',
'POINT(-70.576171875 -35.389049966911664)',
'POINT(-78.74999999999999 -33.431441335575286)',
'POINT(-80.59570312500001 -33.87041555094184)',
'POINT(-72.861328125 -39.77476948529545)',
'POINT(-67.1484375 -35.88905007936091)',
'POINT(-63.544921875 -35.96022296929668)',
'POINT(-59.23828125000001 -34.95799531086792)',
'POINT(-55.89843749999999 -32.472695022061494)',
'POINT(-53.525390625000014 -33.65120829920498)',
'POINT(-58.18359374999999 -37.85750715625202)',
'POINT(-63.017578125 -40.44694705960048)',
'POINT(-68.5546875 -43.19716728250127)',
'POINT(-73.12500000000001 -45.33670190996812)',
'POINT(-67.67578125000003 -46.679594465640186)',
'POINT(-74.8828125 -49.15296965617039)',
'POINT(-69.43359375000001 -49.15296965617039)',
'POINT(-73.82812500000003 -52.16045455774705)',
'POINT(-68.90625000000001 -52.26815737376817)',
'POINT(-68.73046874999999 -55.27911529201561)',
'POINT(-65.390625 -67.57571741708055)',
'POINT(-66.44531250000001 -71.82883956585104)',
'POINT(-72.685546875 -71.60828252210263)',
]

# rest of code of BERA....
# .
# .

# COPY OUTPUT AND PASTE IT IN https://clydedacruz.github.io/openstreetmap-wkt-playground/
for  polygon in new_geoms :
print(polygon , ',')
``````
• Specify a higher resolution parameter than 16 (the default). I also dont think UTM coordinate systems are going to give you perfect buffers.
– Bera
Commented Apr 4 at 6:01
• hi, I edited the anwer for add a solution Commented Apr 4 at 15:29