How to calculate the area of a polygon on the earth's surface using python?

42,309

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.

Share:
42,309
andreas-h
Author by

andreas-h

Updated on June 08, 2021

Comments

  • andreas-h
    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
    Joe Kington over 13 years
    Very 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
    Spacedman over 13 years
    Note 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
    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
    Spacedman over 13 years
    Yeah. Life was so much easier when the earth was flat.
  • Brad Koch
    Brad Koch almost 10 years
    Strictly speaking, that GeoJSON should have a fifth closing point, [-102.05, 41.0]. The spec is a bit vague on these things sometimes.
  • jyf1987
    jyf1987 over 7 years
    what's the unit of that area result? is it in square meters or square inches?
  • spanishgum
    spanishgum over 7 years
    In 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
    Ali almost 7 years
    Does this work just as well for polygons that might not be simple looking quadrilaterals?
  • Ali
    Ali almost 7 years
    Thank 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 with shapely.geometry.Polygon(list(zip(x, y))).area to get the area in sq. meters.
  • Jason
    Jason over 6 years
    I 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
    Jason over 6 years
    So, 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-liner area=np.abs(0.5*np.sum(y[:-1]*np.diff(x) - x[:-1]*np.diff(y))) frees you the need of the shapely module
  • sulkeh
    sulkeh over 6 years
    Could 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
    Jason over 6 years
    I 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
    sulkeh over 6 years
    Ok. 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
    Jason over 6 years
    I 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
    sulkeh over 6 years
    Yes, 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
    blaylockbk about 5 years
    For clarification, what is the source for that equation used to compute the area?
  • Jason
    Jason about 5 years
    @blaylockbk It's Green's theorem.
  • blaylockbk
    blaylockbk about 5 years
    I 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
    rysqui over 4 years
    AFAI 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
    gepcel almost 4 years
    Why should the lon_0=-105.55 other than -106.55 ? mean(lon)=-105.55.
  • swapnil agashe
    swapnil agashe over 3 years
    Which 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
    Jason almost 3 years
    I now prefer this solution over my own, for its not relying on any extra packages. Basemap has been deprecated.
  • Shawn
    Shawn over 2 years
    The 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.