I’m writing today about downloading, handling, and plotting satellite derived air pollution maps with cartopy and fiona using Python. One key task in this post is to clip a raster-like (2-d array) dataset with a polygon in pure Python environment (i.e., no need for ArcGIS or QGIS GUI-based software).
The satellite sensor can offer critical supplementary data of several atmospheric species, e.g., SO2, NO2, PM2.5. Comparaing to ground-based monitoring which might be sparse in some areas (e.g., Africa, South America, oceans), the satellite observation offers a full picture for better understanding the spatiotemporal patterns of some air pollutants.
Below is an excerpt of a NO2 column maps within Chengyu urabn agglomeration in China.
The tropospheric NO2 concentration derived from satellite remote sensing techniques have been used on global, regional and urban scales since the mid-nineties. After the launch of the Ozone Monitoring Instrument (OMI) in 2004, the spatial resolution (up to 13 x 24 km2) ave been largely improved. The vetical column NO2 dataset have contributed to reveal the hotspots of air pollution around the world, or show the episodes with long-range transport.
The TEMIS website provide the image and data for NO2 concentration maps in individual days and monthly averages derived from different satellite sensors (e.g., OMI, GOME-2). Herein, I present the general processes to obtain DOMINO version 2.0 data
Obtaining the datasets
We start by downloading the essential data files using function of wget.
1 2 3 4 5 6 7
#Creat an loop for downloading the monthly averages from 2013-2017 import os for i in ['2013','2014','2015','2016','2017']: for t in ['01','02','03','04','05','06','07','08','09','10','11','12']: filename = 'http://www.temis.nl/airpollution/no2col/data/omi/data_v2/'+i+'/'+t+'/'+'no2_'+i+t+ '.grd.gz' os.system('wget %s' %filename) print"DOWNLOADING COMPLETE"
Merging the datasets
We then define a reading function and open those files to obtain essential information. In the case of 2013, there are 12 monthly averages need to read and merge for further analysis.
set_year = 2013 for i inrange(0,12,1): # setting the file path which contains those original data. file_path = './NO2_POMINO/no2_'+str(set_year)+ '%02d' %(i+1)+'.grd' lon,lat,value,nodata_value = read_grd(file_path) value = value[::-1] value[value == nodata_value] = np.nan if i ==0: value_ = value.reshape(1,value.shape[0],value.shape[1]) else: value_ = np.vstack([value_,value[None, ...]]) ## np.nanmean() is utilized to compute the arithmetic mean igonring NaNs no2_2013 = np.nanmean(value_,axis = 0)
##Cut 2-d array by shapefile
Next, we import the necessary library and the specific shapefile to clip the global 2-d map.
#1. transform the shapefile to fiona polygon object import fiona from shapely.geometry import shape,Polygon, Point cf_area = fiona.open("xxx.shp") pol = cf_area.next() from shapely.geometry import shape geom = shape(pol['geometry']) poly_data = pol["geometry"]["coordinates"][0] poly = Polygon(poly_data)
#2. substract the dataset that can cover the polygon x1 = np.array([t for t,j in (poly_data)]).min() x2 = np.array([t for t,j in (poly_data)]).max() y1 = np.array([j for t,j in (poly_data)]).min() y2 = np.array([j for t,j in (poly_data)]).max() extent = x1 -0.5, x2+0.5,y1-0.5, y2+0.5
#3. test the dat points whether or not within the polygon import time start_time = time.time() sh = (len(lat_)*len(lon_),2) points = np.zeros(len(lat_)*len(lon_)*2).reshape(*sh) k = 0 for i inrange(0,len(lat_),1): for j inrange(0,len(lon_),1): points[k] = np.array([lon_[j],lat_[i]],dtype=float) k+=1 mask = np.array([poly.contains(Point(x, y)) for x, y in points]) mask = mask.reshape(len(lat_),len(lon_)) print("--- %s seconds ---" % (time.time() - start_time))
print mask.shape np.savetxt("mask_SO2.txt",mask)
#4. mask the data points outsied the polygon
defshape_mask(array, lon_,lat_,mask): arr = np.zeros_like(array) for i inrange(0,len(lat_),1): for j inrange(0,len( lon_),1): if mask[i,j] == 1: arr[i,j] = array[i,j] else: arr[i,j] = -1 return arr no2_mask = shape_mask(no2_data, lon_,lat_,mask)
The mask array as the rastered polygon is shown like this.
1 2 3 4 5 6 7
fig = plt.figure() ax = back_main_clean(fig,extent) for spine in plt.gca().spines.values(): spine.set_visible(False) ax.outline_patch.set_visible(False) ax.pcolormesh(mask) plt.tight_layout()
from mpl_toolkits.axes_grid1 import make_axes_locatable fig = plt.figure() ax = back_main_clean(fig,extent) # the background map was prior-defined by specific uses. lon_x,lat_y = np.meshgrid(lon_,lat_)
Kommentare