How to calculate the area of a polygon on the earth's surface using python?
Solution 1
Let's say you have a representation of the state of Colorado in GeoJSON format
{"type": "Polygon",
"coordinates": [[
[-102.05, 41.0],
[-102.05, 37.0],
[-109.05, 37.0],
[-109.05, 41.0]
]]}
All coordinates are longitude, latitude. You can use pyproj to project the coordinates and Shapely to find the area of any projected polygon:
co = {"type": "Polygon", "coordinates": [
[(-102.05, 41.0),
(-102.05, 37.0),
(-109.05, 37.0),
(-109.05, 41.0)]]}
lon, lat = zip(*co['coordinates'][0])
from pyproj import Proj
pa = Proj("+proj=aea +lat_1=37.0 +lat_2=41.0 +lat_0=39.0 +lon_0=-106.55")
That's an equal area projection centered on and bracketing the area of interest. Now make new projected GeoJSON representation, turn into a Shapely geometric object, and take the area:
x, y = pa(lon, lat)
cop = {"type": "Polygon", "coordinates": [zip(x, y)]}
from shapely.geometry import shape
shape(cop).area # 268952044107.43506
It's a very close approximation to the surveyed area. For more complex features, you'll need to sample along the edges, between the vertices, to get accurate values. All caveats above about datelines, etc, apply. If you're only interested in area, you can translate your feature away from the dateline before projecting.
Solution 2
The easiest way to do this (in my opinion), is to project things into (a very simple) equal-area projection and use one of the usual planar techniques for calculating area.
First off, I'm going to assume that a spherical earth is close enough for your purposes, if you're asking this question. If not, then you need to reproject your data using an appropriate ellipsoid, in which case you're going to want to use an actual projection library (everything uses proj4 behind the scenes, these days) such as the python bindings to GDAL/OGR or (the much more friendly) pyproj.
However, if you're okay with a spherical earth, it quite simple to do this without any specialized libraries.
The simplest equal-area projection to calculate is a sinusoidal projection. Basically, you just multiply the latitude by the length of one degree of latitude, and the longitude by the length of a degree of latitude and the cosine of the latitude.
def reproject(latitude, longitude):
"""Returns the x & y coordinates in meters using a sinusoidal projection"""
from math import pi, cos, radians
earth_radius = 6371009 # in meters
lat_dist = pi * earth_radius / 180.0
y = [lat * lat_dist for lat in latitude]
x = [long * lat_dist * cos(radians(lat))
for lat, long in zip(latitude, longitude)]
return x, y
Okay... Now all we have to do is to calculate the area of an arbitrary polygon in a plane.
There are a number of ways to do this. I'm going to use what is probably the most common one here.
def area_of_polygon(x, y):
"""Calculates the area of an arbitrary polygon given its verticies"""
area = 0.0
for i in range(-1, len(x)-1):
area += x[i] * (y[i+1] - y[i-1])
return abs(area) / 2.0
Hopefully that will point you in the right direction, anyway...
Solution 3
A bit late perhaps, but here is a different method, using Girard's theorem. It states that the area of a polygon of great circles is R**2 times the sum of the angles between the polygons minus (N-2)*pi where N is number of corners.
I thought this would be worth posting, since it doesn't rely on any other libraries than numpy, and it is a quite different method than the others. Of course, this only works on a sphere, so there will be some inaccuracy when applying it to the Earth.
First, I define a function to compute the bearing angle from point 1 along a great circle to point 2:
import numpy as np
from numpy import cos, sin, arctan2
d2r = np.pi/180
def greatCircleBearing(lon1, lat1, lon2, lat2):
dLong = lon1 - lon2
s = cos(d2r*lat2)*sin(d2r*dLong)
c = cos(d2r*lat1)*sin(d2r*lat2) - sin(lat1*d2r)*cos(d2r*lat2)*cos(d2r*dLong)
return np.arctan2(s, c)
Now I can use this to find the angles, and then the area (In the following, lons and lats should of course be specified, and they should be in the right order. Also, the radius of the sphere should be specified.)
N = len(lons)
angles = np.empty(N)
for i in range(N):
phiB1, phiA, phiB2 = np.roll(lats, i)[:3]
LB1, LA, LB2 = np.roll(lons, i)[:3]
# calculate angle with north (eastward)
beta1 = greatCircleBearing(LA, phiA, LB1, phiB1)
beta2 = greatCircleBearing(LA, phiA, LB2, phiB2)
# calculate angle between the polygons and add to angle array
angles[i] = np.arccos(cos(-beta1)*cos(-beta2) + sin(-beta1)*sin(-beta2))
area = (sum(angles) - (N-2)*np.pi)*R**2
With the Colorado coordinates given in another reply, and with Earth radius 6371 km, I get that the area is 268930758560.74808
Solution 4
Or simply use a library: https://github.com/scisco/area
from area import area
>>> obj = {'type':'Polygon','coordinates':[[[-180,-90],[-180,90],[180,90],[180,-90],[-180,-90]]]}
>>> area(obj)
511207893395811.06
...returns the area in square meters.
Solution 5
Here is a solution that uses basemap
, instead of pyproj
and shapely
, for the coordinate conversion. The idea is the same as suggested by @sgillies though. NOTE that I've added the 5th point so that the path is a closed loop.
import numpy
from mpl_toolkits.basemap import Basemap
coordinates=numpy.array([
[-102.05, 41.0],
[-102.05, 37.0],
[-109.05, 37.0],
[-109.05, 41.0],
[-102.05, 41.0]])
lats=coordinates[:,1]
lons=coordinates[:,0]
lat1=numpy.min(lats)
lat2=numpy.max(lats)
lon1=numpy.min(lons)
lon2=numpy.max(lons)
bmap=Basemap(projection='cea',llcrnrlat=lat1,llcrnrlon=lon1,urcrnrlat=lat2,urcrnrlon=lon2)
xs,ys=bmap(lons,lats)
area=numpy.abs(0.5*numpy.sum(ys[:-1]*numpy.diff(xs)-xs[:-1]*numpy.diff(ys)))
area=area/1e6
print area
The result is 268993.609651 in km^2.
UPDATE: Basemap has been deprecated, so you may want to consider alternative solutions first.
andreas-h
Updated on June 08, 2021Comments
-
andreas-h almost 3 years
The title basically says it all. I need to calculate the area inside a polygon on the Earth's surface using Python. Calculating area enclosed by arbitrary polygon on Earth's surface says something about it, but remains vague on the technical details:
If you want to do this with a more "GIS" flavor, then you need to select an unit-of-measure for your area and find an appropriate projection that preserves area (not all do). Since you are talking about calculating an arbitrary polygon, I would use something like a Lambert Azimuthal Equal Area projection. Set the origin/center of the projection to be the center of your polygon, project the polygon to the new coordinate system, then calculate the area using standard planar techniques.
So, how do I do this in Python?
-
Joe Kington over 13 yearsVery true! I was just giving a simple example of the most basic case in my answer... It doesn't even remotely try to deal with crossing the prime meridian (in 0-360) or the antimeridian (in -180 to 180).
-
Spacedman over 13 yearsNote that lines will be distorted with a sinusoidal projection. It might be wise to interpolate along great circles between points so that your projected polygon doesn't get distorted.
-
Joe Kington over 13 years@spacedman - Of course! I was just trying to show a simple approximation. It's not intended to be entirely accurate. The distortion of the sides of the polygon will only matter if he's trying to calculate a polygon with very large sides and few verticies, though.
-
Spacedman over 13 yearsYeah. Life was so much easier when the earth was flat.
-
Brad Koch almost 10 yearsStrictly speaking, that GeoJSON should have a fifth closing point,
[-102.05, 41.0]
. The spec is a bit vague on these things sometimes. -
jyf1987 over 7 yearswhat's the unit of that area result? is it in square meters or square inches?
-
spanishgum over 7 yearsIn case anyone else is wondering, it is in square meters. You can google area of colorado and get 104,185 square miles. Converting that to square meters gives roughly the same result.
-
Ali almost 7 yearsDoes this work just as well for polygons that might not be simple looking quadrilaterals?
-
Ali almost 7 yearsThank you so much for that reprojection function, you saved me after a day of being lost! Side note to others: after the reprojection, you can also use Python's
shapely
package to compute the area withshapely.geometry.Polygon(list(zip(x, y))).area
to get the area in sq. meters. -
Jason over 6 yearsI tried your method and got a negative value. Besides, its abs (
139013699.103
) is quite different from the value (809339.212
) I got from using a "grid" method, which is taking all the grid cells inside the polygon from a rectangular Mercator grid, and summing up the grid cell areas. The area of each cell is the product of it zonal and meridional intervals (converted from lat,lon to km). -
Jason over 6 yearsSo, basically I can use any equal-area projection to convert the coordinates and use Green's theorem to compute the area, am I right? If so, then one can alternatively use
basemap
to do the projection (e.g. 'aea', 'cea'). In terms of Green's theorem, a one-linerarea=np.abs(0.5*np.sum(y[:-1]*np.diff(x) - x[:-1]*np.diff(y)))
frees you the need of theshapely
module -
sulkeh over 6 yearsCould you clarify your comment? Where are the numbers you refer to coming from? I tried the code I posted again, and it gives the reported result...
-
Jason over 6 yearsI applied it on a contour found using matplotlib's
contour
function, in fact I have a few tens of them and everyone reported a negative area using your method. -
sulkeh over 6 yearsOk. I still don't understand where the numbers that you mention come from. I would happy to fix any mistake if you can be more specific. Can you post an example of lons/lats that give a negative area?
-
Jason over 6 yearsI exported one contour coordinates here pastebin.com/2fMkFgvu maybe you could have a try. One thing I noticed that if I sub-sample the 135-point contour by taking every 5 points (
x=x[::5]; y=y[::5]
), I could get a positive value that is close to the 'true' value. -
sulkeh over 6 yearsYes, I get negative area too with your example. I'm pretty sure that the reason is rounding errors when calculating the angles, since the points are very close. This is why it works if you sub-sample. So it's not a robust method at high precision, which is not very surprising. Thanks for pointing it out though
-
blaylockbk about 5 yearsFor clarification, what is the source for that equation used to compute the area?
-
Jason about 5 years@blaylockbk It's Green's theorem.
-
blaylockbk about 5 yearsI like that this method lets me use latitude/longitude points directly, and accounts for the curvature of the earth without needing to project the points on a Cartesian grid. I compared the results from this method and the standard Green's theorem to the areas of different states on Google and found that this library gave the most accurate results. Thanks!
-
rysqui over 4 yearsAFAI understand, this library is calculating geodesic area assuming the earth is a perfect sphere with radius defined as
WGS84_RADIUS = 6378137
Just keep that in mind. -
gepcel almost 4 yearsWhy should the
lon_0=-105.55
other than -106.55 ?mean(lon)=-105.55
. -
swapnil agashe over 3 yearsWhich two points are taken in the string "+proj=aea +lat_1=37.0 +lat_2=41.0 +lat_0=39.0 +lon_0=-106.55", I assume one of them would be the centroid of the polygon but what's the other point?
-
Jason almost 3 yearsI now prefer this solution over my own, for its not relying on any extra packages. Basemap has been deprecated.
-
Shawn over 2 yearsThe border of Colorado is not a perfect square, there is a small "bulge" on the western side from imperfect surveys when the border was establish. The census area probably accounts for this and is why all areas here underestimate it slightly.