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

前端 未结 9 506
生来不讨喜
生来不讨喜 2020-11-29 19:09

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&

相关标签:
9条回答
  • 2020-11-29 19:30

    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.

    0 讨论(0)
  • 2020-11-29 19:37

    You can compute the area directly on the sphere, instead of using an equal-area projection.

    Moreover, according to this discussion, it seems that Girard's theorem (sulkeh's answer) does not give accurate results in certain cases, for example "the area enclosed by a 30º lune from pole to pole and bounded by the prime meridian and 30ºE" (see here).

    A more precise solution would be to perform line integral directly on the sphere. The comparison below shows this method is more precise.

    Like all other answers, I should mention the caveat that we assume a spherical earth, but I assume that for non-critical purposes this is enough.

    Python implementation

    Here is a Python 3 implementation which uses line integral and Green's theorem:

    def polygon_area(lats, lons, radius = 6378137):
        """
        Computes area of spherical polygon, assuming spherical Earth. 
        Returns result in ratio of the sphere's area if the radius is specified.
        Otherwise, in the units of provided radius.
        lats and lons are in degrees.
        """
        from numpy import arctan2, cos, sin, sqrt, pi, power, append, diff, deg2rad
        lats = np.deg2rad(lats)
        lons = np.deg2rad(lons)
    
        # Line integral based on Green's Theorem, assumes spherical Earth
    
        #close polygon
        if lats[0]!=lats[-1]:
            lats = append(lats, lats[0])
            lons = append(lons, lons[0])
    
        #colatitudes relative to (0,0)
        a = sin(lats/2)**2 + cos(lats)* sin(lons/2)**2
        colat = 2*arctan2( sqrt(a), sqrt(1-a) )
    
        #azimuths relative to (0,0)
        az = arctan2(cos(lats) * sin(lons), sin(lats)) % (2*pi)
    
        # Calculate diffs
        # daz = diff(az) % (2*pi)
        daz = diff(az)
        daz = (daz + pi) % (2 * pi) - pi
    
        deltas=diff(colat)/2
        colat=colat[0:-1]+deltas
    
        # Perform integral
        integrands = (1-cos(colat)) * daz
    
        # Integrate 
        area = abs(sum(integrands))/(4*pi)
    
        area = min(area,1-area)
        if radius is not None: #return in units of radius
            return area * 4*pi*radius**2
        else: #return in ratio of sphere total area
            return area
    

    I wrote a somewhat more explicit version (and with many more references and TODOs...) in the sphericalgeometry package there.

    Numerical Comparison

    Colorado will be the reference, since all previous answers were evaluated on its area. Its precise total area is 104,093.67 square miles (from the US Census Bureau, p. 89, see also here), or 269601367661 square meters. I found no source for the actual methodology of the USCB, but I assume it is based on summing actual measurements on ground, or precise computations using WGS84/EGM2008.

    Method                 | Author     | Result       | Variation from ground truth
    --------------------------------------------------------------------------------
    Albers Equal Area      | sgillies   | 268952044107 | -0.24%
    Sinusoidal             | J. Kington | 268885360163 | -0.26%
    Girard's theorem       | sulkeh     | 268930758560 | -0.25%
    Equal Area Cylindrical | Jason      | 268993609651 | -0.22%
    Line integral          | Yellows    | 269397764066 | **-0.07%**
    

    Conclusion: using direct integral is more precise.

    Performance

    I have not benchmarked the different methods, and comparing pure Python code with compiled PROJ projections would not be meaningful. Intuitively less computations are needed. On the other hand, trigonometric functions may be computationally intensive.

    0 讨论(0)
  • 2020-11-29 19:40

    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.

    0 讨论(0)
  • 2020-11-29 19:43

    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

    0 讨论(0)
  • 2020-11-29 19:48

    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.

    0 讨论(0)
  • 2020-11-29 19:50

    Because the earth is a closed surface a closed polygon drawn on its surface creates TWO polygonal areas. You also need to define which one is inside and which is outside!

    Most times people will be dealing with small polygons, and so it's 'obvious' but once you have things the size of oceans or continents, you better make sure you get this the right way round.

    Also, remember that lines can go from (-179,0) to (+179,0) in two different ways. One is very much longer than the other. Again, mostly you'll make the assumption that this is a line that goes from (-179,0) to (-180,0) which is (+180,0) and then to (+179,0), but one day... it won't.

    Treating lat-long like a simple (x,y) coordinate system, or even neglecting the fact that any coordinate projection is going to have distortions and breaks, can make you fail big-time on spheres.

    0 讨论(0)
提交回复
热议问题