Reputation: 2854
I have a need to do some integration/summation over gridded weather data (rainfall), in standard geographic coordinates. I therefore need to calculate area elements associated with each of my grid points. Basically the real areas of the grid cells. We're talking a standard, equally-spaced, rectilinear lat/lon grid here.
How can I do this in Python?
I know I can get them reasonably accurately just using a spherical coordinates approximation, but this has to be super standard, so might as well use a package with a proper Earth shape model in it. However I've searched around a lot and not found much on how to do this basic task in the common geospatial packages. Seems like it's generally hidden under the hood or something, handled internally. I'm sure it can be dug out somehow though.
Edit: A hand-crafted solution is here: How to calculate size in m2 of each lat/long grid square, but I'd rather use a standard library if possible.
Upvotes: 1
Views: 806
Reputation: 2854
Olivier's answer is great and concise, but for my own curiosity I wanted to see how it compared to a more accurate calculation (their answer assumes the grid cells are small enough to approximate as squares):
import numpy as np
from pyproj import Geod
from shapely.geometry import LineString, Point, Polygon
lons = np.arange(0,20,0.1)
lats = np.arange(30,45,0.1)
# Fast way, square approx of grid cells
geod = Geod(ellps="WGS84")
lon2D,lat2D = np.meshgrid(lons, lats)
_,_, distEW = geod.inv(lon2D[:,:-1],lat2D[:,1:], lon2D[:,1:], lat2D[:,1:])
_,_, distNS = geod.inv(lon2D[1:,:],lat2D[1:,:], lon2D[1:,:], lat2D[:-1,:])
square_area = distEW[1:,:] * distNS[:,1:]
# Slower way, geodesic areas
def stack_coords(x, y):
"""Stacks coordinate lists into a 2D array of coordinate pairs,
flattened to list of coordinate pairs"""
out = np.vstack(np.stack(np.meshgrid(x, y), axis=2))
return out
def get_lat_squares(lats, lons):
"""Construct shapely Polygons for a single longitude but
every latitude.
Area is constant with longitude so just copy results.
"""
lats_0 = lats[:-1]
lats_1 = lats[1:]
lons_0 = lons[0:1]
lons_1 = lons[1:2]
c1 = stack_coords(lons_0, lats_0)
c2 = stack_coords(lons_1, lats_0)
c3 = stack_coords(lons_1, lats_1)
c4 = stack_coords(lons_0, lats_1)
squares = []
for p1, p2, p3, p4 in zip(c1, c2, c3, c4):
squares.append(Polygon([p1, p2, p3, p4]))
return squares
def get_areas(lats, lons):
squares = get_lat_squares(lats, lons)
geod = Geod(ellps="WGS84")
areas = []
for square in squares:
area = geod.geometry_area_perimeter(square)[0]
areas.append(area)
return np.array(areas)
geodesic_areas = get_areas(lats, lons)
for a1, a2 in zip(geodesic_areas, square_area[:,0]):
print(a1, a2)
Output:
106904546.2618103 106850809.52460858
106798611.31295013 106744711.14938568
106692351.02400208 106638287.58503169
106585765.66596985 106531539.10307406
106478855.51091766 106424465.9760592
...
88300037.70746613 88224423.89253764
88150258.98899078 88074508.72317643
88000204.78475189 87924318.28878595
87849875.51327515 87773853.00843135
Pretty close! It obviously depends on the size of the grid steps though. I'm still curious about other answers to the question, I did say "area elements" in the question, I guess I was imagining extracting Jacobian factors from some coordinate transform in e.g. pyproj. The shape integrals here should be more accurate of course due to finite size, but I guess I thought it would be faster and easier to get the Jacobian factors... seems like they aren't easily exposed though?
Upvotes: 1
Reputation: 71
See pyproj.Geod.inv, that can be used to return the distance (in meters) between 2 points (in lon/lat).
g=Geod(ellps='WGS84')
lon2D,lat2D = np.meshgrid(np.arange(0,20,0.1),np.arange(30,45,0.1))
_,_, distEW = g.inv(lon2D[:,:-1],lat2D[:,1:], lon2D[:,1:], lat2D[:,1:])
_,_, distNS = g.inv(lon2D[1:,:],lat2D[1:,:], lon2D[1:,:], lat2D[:-1,:])
pixel_area = distEW[1:,:] * distNS[:,1:]
Upvotes: 2