1

I have a ABI-L1b radiance file in the *.nc file format. I'm trying to cut out a selection of this file with a shapely polygon. I can plot both the netCDF4 file and polygon but I am unsure of how to cut out the data. Here is the code and a plot

#!/usr/bin/python
# See https://makersportal.com/blog/2018/11/25/goes-r-satellite-latitude-and-longitude-grid-projection-algorithm

from netCDF4 import Dataset
import matplotlib as mpl

mpl.use("TkAgg")
import pyproj
from functools import partial
from shapely.geometry import Point
from shapely.geometry import Polygon
from shapely.ops import transform
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap, cm
import numpy as np
import os


# Site of the fire center
l = {"latitude": 40.109, "longitude": -122.789}

# Drawing km circle around what we're interested in
def geodesic_point_buffer(lat, lon, km):
    proj_wgs84 = pyproj.Proj(init="epsg:4326")
    # Azimuthal equidistant projection
    aeqd_proj = "+proj=aeqd +lat_0={lat} +lon_0={lon} +x_0=0 +y_0=0"
    project = partial(
        pyproj.transform, pyproj.Proj(aeqd_proj.format(lat=lat, lon=lon)), proj_wgs84
    )
    buf = Point(0, 0).buffer(km * 1000)  # distance in meters
    return transform(project, buf).exterior.coords[:]


def lat_lon_reproj(nc_folder, nc_indx):
    os.chdir(nc_folder)
    full_direc = os.listdir()
    nc_files = [ii for ii in full_direc if ii.endswith(".nc")]
    g16_data_file = nc_files[nc_indx]  # select .nc file
    print(nc_files[nc_indx])  # print file name

    # designate dataset
    g16nc = Dataset(g16_data_file, "r")
    var_names = [ii for ii in g16nc.variables]
    var_name = var_names[0]
    try:
        band_id = g16nc.variables["band_id"][:]
        band_id = " (Band: {},".format(band_id[0])
        band_wavelength = g16nc.variables["band_wavelength"]
        band_wavelength_units = band_wavelength.units
        band_wavelength_units = "{})".format(band_wavelength_units)
        band_wavelength = " {0:.2f} ".format(band_wavelength[:][0])
        print("Band ID: {}".format(band_id))
        print("Band Wavelength: {} {}".format(band_wavelength, band_wavelength_units))
    except:
        band_id = ""
        band_wavelength = ""
        band_wavelength_units = ""

    # GOES-R projection info and retrieving relevant constants
    proj_info = g16nc.variables["goes_imager_projection"]
    lon_origin = proj_info.longitude_of_projection_origin
    H = proj_info.perspective_point_height + proj_info.semi_major_axis
    r_eq = proj_info.semi_major_axis
    r_pol = proj_info.semi_minor_axis

    # grid info
    lat_rad_1d = g16nc.variables["x"][:]
    lon_rad_1d = g16nc.variables["y"][:]

    # data info
    data = g16nc.variables[var_name][:]
    data_units = g16nc.variables[var_name].units
    data_time_grab = ((g16nc.time_coverage_end).replace("T", " ")).replace("Z", "")
    data_long_name = g16nc.variables[var_name].long_name

    # close file when finished
    g16nc.close()
    g16nc = None

    # create meshgrid filled with radian angles
    lat_rad, lon_rad = np.meshgrid(lat_rad_1d, lon_rad_1d)

    # lat/lon calc routine from satellite radian angle vectors

    lambda_0 = (lon_origin * np.pi) / 180.0

    a_var = np.power(np.sin(lat_rad), 2.0) + (
        np.power(np.cos(lat_rad), 2.0)
        * (
            np.power(np.cos(lon_rad), 2.0)
            + (((r_eq * r_eq) / (r_pol * r_pol)) * np.power(np.sin(lon_rad), 2.0))
        )
    )
    b_var = -2.0 * H * np.cos(lat_rad) * np.cos(lon_rad)
    c_var = (H ** 2.0) - (r_eq ** 2.0)

    r_s = (-1.0 * b_var - np.sqrt((b_var ** 2) - (4.0 * a_var * c_var))) / (2.0 * a_var)

    s_x = r_s * np.cos(lat_rad) * np.cos(lon_rad)
    s_y = -r_s * np.sin(lat_rad)
    s_z = r_s * np.cos(lat_rad) * np.sin(lon_rad)

    lat = (180.0 / np.pi) * (
        np.arctan(
            ((r_eq * r_eq) / (r_pol * r_pol))
            * ((s_z / np.sqrt(((H - s_x) * (H - s_x)) + (s_y * s_y))))
        )
    )
    lon = (lambda_0 - np.arctan(s_y / (H - s_x))) * (180.0 / np.pi)

    return (
        lon,
        lat,
        data,
        data_units,
        data_time_grab,
        data_long_name,
        band_id,
        band_wavelength,
        band_wavelength_units,
        var_name,
    )


nc_folder = "/path/to/ABI-L1b-radiances"  # define folder where .nc files are located
nc_folder = "/home/n/Documents/Research/Fire-Prediction/data/2019/248/00"

file_indx = (
    0  # be sure to pick the correct file. Make sure the file is not too big either,
)

# main data grab from function above
(
    lon,
    lat,
    data,
    data_units,
    data_time_grab,
    data_long_name,
    band_id,
    band_wavelength,
    band_units,
    var_name,
) = lat_lon_reproj(nc_folder, file_indx)

flat_lon = lon.flatten()
flat_lat = lat.flatten()
flat_data = data.flatten()

b = geodesic_point_buffer(l["latitude"], l["longitude"], 5)
b_x, b_y = zip(*b)
poly_km = Polygon(b)

bbox = [np.min(lon), np.min(lat), np.max(lon), np.max(lat)]  # set bounds for plotting

# figure routine for visualization
fig = plt.figure(figsize=(9, 4), dpi=200)

n_add = 0
m = Basemap(
    resolution="i",
    projection="cyl",
    area_thresh=50000,
    llcrnrlon=l["longitude"] - 1,
    llcrnrlat=l["latitude"] - 1,
    urcrnrlon=l["longitude"] + 1,
    urcrnrlat=l["latitude"] + 1,
)

m.drawcoastlines(linewidth=0.5)
m.drawcountries(linewidth=0.25)
m.pcolormesh(lon.data, lat.data, data, latlon=True)
parallels = np.linspace(np.min(lat), np.max(lat), 5)
m.drawparallels(parallels, labels=[True, False, False, False])
meridians = np.linspace(np.min(lon), np.max(lon), 5)
m.drawmeridians(meridians, labels=[False, False, False, True])
cb = m.colorbar()

data_units = ((data_units.replace("-", "^{-")).replace("1", "1}")).replace("2", "2}")
plt.rc("text", usetex=True)
cb.set_label(r"%s $ \left[ \mathrm{%s} \right] $" % (var_name, data_units))
plt.title(
    "{0}{2}{3}{4} on {1}".format(
        data_long_name, data_time_grab, band_id, band_wavelength, band_units
    )
)
plt.tight_layout()

m_bx, m_by = m(b_x, b_y)

m.plot(b_x, b_y, c="r", linewidth=0.1)

plt.show()

I get the following image: enter image description here

So I need to cut out the data in the red circle from the image. I've tried looking into GDAL python cut geotiff image with geojson file. So I convert the .nc file to .tif using gtranslate then using rasterio intersect the geometry with the .tif file. However, I keep getting that the geometry does not intersect the raster file. Am I going about this the wrong way?

Here is a dropbox link to the .nc file: https://www.dropbox.com/s/nyj6zpdvubgqppb/glm.nc?dl=0

snowman2
  • 7,321
  • 12
  • 29
  • 54
kauii8
  • 113
  • 4
  • Hey, welcome to GIS. Does the geometry share the same spatial reference as the raster file? – Marcelo Villa Mar 03 '20 at 23:36
  • Thanks for the response! So actually this is perhaps where I'm messing up. My geometry is in lon lat coordinates, but I'm not sure what the spatial reference of the raster file is. Is there an easy way to find that out? When I type gdalinfo on the .tif file I get this: Coordinate System is: GEOGCS["WGS 84", DATUM["WGS_1984", SPHEROID["WGS 84",6378137,298.257223563, AUTHORITY["EPSG","7030"]], AUTHORITY["EPSG","6326"]], PRIMEM["Greenwich",0], UNIT["degree",0.0174532925199433], AUTHORITY["EPSG","4326"]] – kauii8 Mar 04 '20 at 01:38
  • It seems both spatial references are WGS84. Maybe including the code you are using to do the intersection could be helpful. – Marcelo Villa Mar 04 '20 at 03:26
  • Are you able to share the source file? You may also be interested in: https://corteva.github.io/rioxarray/stable/examples/clip_geom.html – snowman2 Mar 04 '20 at 03:56

1 Answers1

1

You may be interested in rioxarray.

Here is a link to an answer for finding the GOES 16 satellite file projection information: https://gis.stackexchange.com/a/345697/144357

from functools import partial

from pyproj import CRS
from pyproj import Transformer
from shapely.geometry import Point, mapping
from shapely.ops import transform
import rioxarray # for 'rio' accessor
import xarray


xds = xarray.open_dataset("glm.nc")
goes_json = CRS.from_cf(xds.goes_imager_projection.attrs).to_json_dict()
sat_height = xds.goes_imager_projection.attrs["perspective_point_height"]
unit_deg = {"type": "LinearUnit", "name": "Deg", "conversion_factor": sat_height}
goes_json["coordinate_system"] = {
    "subtype": "Cartesian",
    "axis": [
      {
        "name": "Easting",
        "abbreviation": "E",
        "direction": "east",
        "unit": unit_deg
      },
      {
        "name": "Northing",
        "abbreviation": "N",
        "direction": "north",
        "unit": unit_deg
      }
    ]
}
xds.rio.write_crs(goes_json, inplace=True)

Generate the buffer:

# Drawing km circle around what we're interested in
def geodesic_point_buffer(lat, lon, km):
    # Azimuthal equidistant projection
    aeqd_proj = f"+proj=aeqd +lat_0={lat} +lon_0={lon} +x_0=0 +y_0=0"
    transformer = Transformer.from_crs("EPSG:4326", aeqd_proj, always_xy=True)
    point = Point(lon, lat)
    point_aeqd = transform(transformer.transform, point)
    circle_aeqd = point_aeqd.buffer(km * 1000)
    return mapping(transform(partial(transformer.transform, direction="INVERSE"), circle_aeqd))

After that, you can use rioxarray to clip: https://corteva.github.io/rioxarray/stable/examples/clip_geom.html

Note: Only Rad and DQF had the x and y coordinates as far as I could tell, so I pulled them out.

buffered_point = geodesic_point_buffer(lat=40.109, lon=-122.789, km=5)
clipped_xds = xds[["Rad", "DQF"]].rio.clip([buffered_point], "EPSG:4326")

Rad plot:

clipped_xds.Rad.plot()

enter image description here

snowman2
  • 7,321
  • 12
  • 29
  • 54
  • Thanks for the answer! I've added a link to the source file. I'm trying to use the xds.rio.clip function and I think you are right this would be a good way to go about it. However, I'm still having trouble clipping the geometry I keep getting: File "rasterio/_features.pyx", line 625, in rasterio._features.OGRGeomBuilder.build TypeError: string indices must be integers" – kauii8 Mar 08 '20 at 22:30
  • I haven't tried out the new file, but I did update the clip example - made it a list as that is what is needed. – snowman2 Mar 09 '20 at 00:57
  • Hmm, I actually tried that earlier but I keep getting: rioxarray.exceptions.NoDataInBounds: No data found in bounds. It's weird because it should intersect in the bounds? – kauii8 Mar 09 '20 at 21:51
  • Just updated the answer with a solution that seems to work. – snowman2 Mar 10 '20 at 02:09
  • Wow! Thanks, this is really helpful, I was stuck on this for a while! Thanks, again – kauii8 Mar 10 '20 at 02:27
  • Hopefully it works for you as well – snowman2 Mar 11 '20 at 01:22