Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GOES-R GLM L2 Gridded product reader and small ABI L1b changes #854

Merged
merged 18 commits into from
Dec 11, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The following people have made contributions to this project:
- [Suyash Behera (Suyash458)](https://github.com/Suyash458)
- [Andrew Brooks (howff)](https://github.com/howff)
- Guido della Bruna - meteoswiss
- [Eric Bruning (deeplycloudy)](https://github.com/deeplycloudy)
- [Lorenzo Clementi (loreclem)](https://github.com/loreclem)
- [Colin Duff (ColinDuff)](https://github.com/ColinDuff)
- [Radar, Satellite and Nowcasting Division (meteoswiss-mdr)](https://github.com/meteoswiss-mdr)
Expand Down
45 changes: 45 additions & 0 deletions satpy/etc/readers/glm_l2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
reader:
description: NetCDF4 reader for GOES-R series GLM data
name: glm_l2
sensors: [glm]
reader: !!python/name:satpy.readers.yaml_reader.FileYAMLReader
# file pattern keys to sort files by with 'satpy.utils.group_files'
group_keys: ['start_time', 'platform_shortname', 'scene_abbr']

# Typical filenames from Unidata THREDDS server:
# http://thredds.unidata.ucar.edu/thredds/catalog/satellite/goes/east/
# products/GeostationaryLightningMapper/CONUS/current/catalog.html
# OR_GLM-L2-GLMC-M3_G16_s20191920000000_e20191920001000_c20191920001380.nc

file_types:
glm_l2_imagery:
file_reader: !!python/name:satpy.readers.glm_l2.NC_GLM_L2_IMAGERY
file_patterns: ['{system_environment:2s}_{mission_id:3s}-L2-{scene_abbr:s}-{scan_mode:2s}_{platform_shortname:3s}_s{start_time:%Y%j%H%M%S%f}_e{end_time:%Y%j%H%M%S%f}_c{creation_time:%Y%j%H%M%S%f}.nc']
djhoese marked this conversation as resolved.
Show resolved Hide resolved
# glm_l2_lcfa — add this with glmtools

datasets:
flash_extent_density:
name: flash_extent_density
file_type: glm_l2_imagery
group_extent_density:
name: group_extent_density
file_type: glm_l2_imagery
flash_centroid_density:
name: flash_centroid_density
file_type: glm_l2_imagery
group_centroid_density:
name: group_centroid_density
file_type: glm_l2_imagery
average_flash_area:
name: average_flash_area
file_type: glm_l2_imagery
minimum_flash_area:
name: minimum_flash_area
file_type: glm_l2_imagery
average_group_area:
name: average_group_area
file_type: glm_l2_imagery
total_energy:
name: total_energy
file_type: glm_l2_imagery

155 changes: 155 additions & 0 deletions satpy/readers/glm_l2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import logging
from datetime import datetime

import numpy as np
import xarray as xr

from pyresample import geometry
from satpy.readers.file_handlers import BaseFileHandler
from satpy import CHUNK_SIZE

logger = logging.getLogger(__name__)

PLATFORM_NAMES = {
'G16': 'GOES-16',
'G17': 'GOES-17',
}

#class NC_GLM_L2_LCFA(BaseFileHandler): — add this with glmtools


class NC_GLM_L2_IMAGERY(BaseFileHandler):
def __init__(self, filename, filename_info, filetype_info):
super(NC_GLM_L2_IMAGERY, self).__init__(filename, filename_info, filetype_info)
# xarray's default netcdf4 engine.
# It includes handling of the _Unsigned attribute, so that the default
# mask_and_scale behavior is correct.
self.nc = xr.open_dataset(self.filename,
decode_cf=True,
mask_and_scale=True,
chunks={'x': CHUNK_SIZE, 'y': CHUNK_SIZE})
# self.nc = self.nc.rename({'t': 'time'})
platform_shortname = filename_info['platform_shortname']
self.platform_name = PLATFORM_NAMES.get(platform_shortname)
self.sensor = 'glm'
self.nlines, self.ncols = self.nc["DQF"].shape
self.coords = {}

def get_shape(self, key, info):
"""Get the shape of the data."""
return self.nlines, self.ncols

def get_area_def(self, key):
"""Get the area definition of the data at hand."""
projection = self.nc["goes_imager_projection"]
a = projection.attrs['semi_major_axis']
h = projection.attrs['perspective_point_height']
b = projection.attrs['semi_minor_axis']

lon_0 = projection.attrs['longitude_of_projection_origin']
sweep_axis = projection.attrs['sweep_angle_axis'][0]

# x and y extents in m
h = float(h)
x = self['x']
y = self['y']
x_l = h * x[0]
x_r = h * x[-1]
y_l = h * y[-1]
y_u = h * y[0]
x_half = (x_r - x_l) / (self.ncols - 1) / 2.
y_half = (y_u - y_l) / (self.nlines - 1) / 2.
area_extent = (x_l - x_half, y_l - y_half, x_r + x_half, y_u + y_half)

proj_dict = {'a': float(a),
'b': float(b),
'lon_0': float(lon_0),
'h': h,
'proj': 'geos',
'units': 'm',
'sweep': sweep_axis}

# GLM imagery reuses the ABI imagery definitions.
area = geometry.AreaDefinition(
self.nc.attrs.get('orbital_slot', 'abi_geos'),
self.nc.attrs.get('spatial_resolution', 'ABI L1B file area'),
'abi_geos',
proj_dict,
self.ncols,
self.nlines,
np.asarray(area_extent))

return area

@property
def start_time(self):
return datetime.strptime(self.nc.attrs['time_coverage_start'], '%Y-%m-%dT%H:%M:%SZ')

@property
def end_time(self):
return datetime.strptime(self.nc.attrs['time_coverage_end'], '%Y-%m-%dT%H:%M:%SZ')

def __getitem__(self, item):
"""Wrapper around `self.nc[item]`. Unlike ABI, we like what xarray
does with our data, so just pass it through.
"""
logger.debug(item)
# logger.debug(self.nc)
return self.nc[item]

def get_dataset(self, key, info):
"""Load a dataset."""
logger.debug('Reading in get_dataset %s.', key.name)
res = self[key.name]

# convert to satpy standard units
if res.attrs['units'] == '1':
res *= 100
res.attrs['units'] = '%'

res.attrs.update({'platform_name': self.platform_name,
'sensor': self.sensor,
'satellite_latitude': float(self['nominal_satellite_subpoint_lat']),
'satellite_longitude': float(self['nominal_satellite_subpoint_lon']),
# 'satellite_altitude': float(self['nominal_satellite_height']),
})

# Add orbital parameters
projection = self.nc["goes_imager_projection"]
res.attrs['orbital_parameters'] = {
'projection_longitude': float(projection.attrs['longitude_of_projection_origin']),
'projection_latitude': float(projection.attrs['latitude_of_projection_origin']),
'projection_altitude': float(projection.attrs['perspective_point_height']),
'satellite_nominal_latitude': float(self['nominal_satellite_subpoint_lat']),
'satellite_nominal_longitude': float(self['nominal_satellite_subpoint_lon']),
# 'satellite_nominal_altitude': float(self['nominal_satellite_height']),
'yaw_flip': False,
}

res.attrs.update(key.to_dict())
# remove attributes that could be confusing later
res.attrs.pop('_FillValue', None)
res.attrs.pop('scale_factor', None)
res.attrs.pop('add_offset', None)
res.attrs.pop('_Unsigned', None)
res.attrs.pop('ancillary_variables', None) # Can't currently load DQF
# add in information from the filename that may be useful to the user
# for key in ('observation_type', 'scene_abbr', 'scan_mode', 'platform_shortname'):
for key in ('scene_abbr', 'scan_mode', 'platform_shortname'):
res.attrs[key] = self.filename_info[key]
# copy global attributes to metadata
for key in ('scene_id', 'orbital_slot', 'instrument_ID', 'production_site', 'timeline_ID'):
res.attrs[key] = self.nc.attrs.get(key)
# only include these if they are present
for key in ('fusion_args',):
if key in self.nc.attrs:
res.attrs[key] = self.nc.attrs[key]

return res

def __del__(self):
try:
self.nc.close()
except (IOError, OSError, AttributeError):
pass