Skip to content

Commit df8234f

Browse files
Merge pull request #87 from ninahakansson/split_files
Split VGAC files at midnight
2 parents 2b5d1ae + d368ec7 commit df8234f

11 files changed

+172
-52
lines changed

.github/workflows/ci.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ jobs:
1010
fail-fast: true
1111
matrix:
1212
os: ["ubuntu-latest", "macos-latest"]
13-
python-version: ["3.9", "3.10", "3.11"]
13+
python-version: ["3.9", "3.11", "3.12"]
1414
experimental: [false]
1515
include:
16-
- python-version: "3.10"
16+
- python-version: "3.12"
1717
os: "ubuntu-latest"
1818
experimental: true
1919

bin/vgac2pps.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@
5353
parser.add_argument('-on', '--orbit_number', type=int, nargs='?',
5454
required=False, default=0,
5555
help="Orbit number (default is 00000).")
56-
56+
parser.add_argument('--don_split_files_at_midnight', action='store_true',
57+
help="Don't split files at midnight, keep as one level1c file.")
5758
options = parser.parse_args()
5859
process_one_scene(options.files, options.out_dir, engine=options.nc_engine,
5960
all_channels=options.all_channels, pps_channels=options.pps_channels,
60-
orbit_n=options.orbit_number, as_noaa19=options.as_noaa19, avhrr_channels=options.avhrr_channels)
61+
orbit_n=options.orbit_number, as_noaa19=options.as_noaa19, avhrr_channels=options.avhrr_channels,
62+
split_files_at_midnight = not options.don_split_files_at_midnight)

continuous_integration/environment.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ dependencies:
77
- h5py
88
- python-geotiepoints
99
- mock
10-
- numpy
10+
- numpy<2.0.0
1111
- satpy>0.41.1
1212
- pyspectral
1313
- h5netcdf

level1c4pps/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -553,8 +553,9 @@ def compose_filename(scene, out_path, instrument, band=None):
553553
end_time = band.attrs['end_time']
554554
platform_name = scene.attrs['platform']
555555
orbit_number = int(scene.attrs['orbit_number'])
556+
out_path_with_dates = start_time.strftime(out_path)
556557
filename = os.path.join(
557-
out_path,
558+
out_path_with_dates,
558559
"S_NWC_{:s}_{:s}_{:05d}_{:s}Z_{:s}Z.nc".format(
559560
instrument,
560561
platform_name_to_use_in_filename(platform_name),

level1c4pps/eumgacfdr2pps_lib.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@
3535
logger,
3636
get_header_attrs, convert_angles)
3737
from satpy.utils import debug_on
38-
from distutils.version import LooseVersion
38+
from packaging.version import Version
3939

40-
if LooseVersion(satpy.__version__) < LooseVersion('0.24.0'):
40+
if Version(satpy.__version__) < Version('0.24.0'):
4141
debug_on()
4242
raise ImportError("'eumgac2pps' writer requires satpy 0.24.0 or greater")
4343
# import xarray as xr

level1c4pps/gac2pps_lib.py

+9
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,17 @@
3939
get_header_attrs, convert_angles)
4040
import logging
4141

42+
from packaging.version import Version
4243
logger = logging.getLogger('gac2pps')
4344

45+
if Version(np.__version__) >= Version('2.0.0'):
46+
if Version(pygac.__version__) == Version('1.7.3'):
47+
raise ImportError("pygac 1.7.3 requires numpy < 2.0.0")
48+
else:
49+
logger.warning("pygac 1.7.3 requires numpy < 2.0.0 or greater")
50+
51+
52+
4453
BANDNAMES = ['1', '2', '3', '3a', '3b', '4', '5']
4554

4655
REFL_BANDS = ['1', '2', '3a']

level1c4pps/slstr2pps_lib.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@
3636
import pyspectral # testing that pyspectral is available # noqa: F401
3737
import logging
3838
from satpy.utils import debug_on
39+
from packaging.version import Version
3940

40-
from distutils.version import LooseVersion
41-
if LooseVersion(satpy.__version__) < LooseVersion('0.22.1'):
41+
if Version(satpy.__version__) < Version('0.22.1'):
4242
raise ImportError("'slstr2pps' requires satpy 0.22.1 or greater")
4343

4444
debug_on()
Binary file not shown.

level1c4pps/tests/test_vgac2pps.py

+31
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,34 @@ def test_process_one_scene_n19(self):
156156

157157
np.testing.assert_equal(pps_nc.__dict__["platform"], "vgac20")
158158
self.assertTrue(np.abs(pps_nc.variables['image1'][0,0,0] - pps_nc_viirs.variables['image1'][0,0,0])>0.01)
159+
160+
def test_process_one_scene_midnight(self):
161+
"""Test process one scene for one example file."""
162+
163+
vgac2pps.process_one_scene(
164+
['./level1c4pps/tests/VGAC_VNPP02MOD_A2012365_2304_n06095_K005.nc'],
165+
out_path='./level1c4pps/tests/',
166+
)
167+
filename = './level1c4pps/tests/S_NWC_viirs_npp_00000_20121230T2359563Z_20121230T2359599Z.nc'
168+
# written with hfnetcdf read with NETCDF4 ensure compatability
169+
pps_nc = netCDF4.Dataset(filename, 'r', format='NETCDF4') # Check compatability implicitly
170+
171+
for key in ['start_time', 'end_time', 'history', 'instrument',
172+
'orbit_number', 'platform',
173+
'sensor', 'source']:
174+
if key not in pps_nc.__dict__.keys():
175+
print("Missing in attributes:", key)
176+
self.assertTrue(key in pps_nc.__dict__.keys())
177+
178+
expected_vars = ['satzenith', 'azimuthdiff',
179+
'satazimuth', 'sunazimuth', 'sunzenith',
180+
'lon', 'lat',
181+
'image1', 'image2', 'image3', 'image4', 'image5',
182+
'image6', 'image7', 'image8', 'image9',
183+
'scanline_timestamps', 'time', 'time_bnds']
184+
for var in expected_vars:
185+
self.assertTrue(var in pps_nc.variables.keys())
186+
187+
print(pps_nc.variables['image1'].shape)
188+
189+
np.testing.assert_equal(pps_nc.variables['image1'].shape, (1, 7, 801))

level1c4pps/vgac2pps_lib.py

+118-41
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from level1c4pps import (get_encoding, compose_filename,
3131
set_header_and_band_attrs_defaults,
3232
rename_latitude_longitude,
33+
dt64_to_datetime,
3334
update_angle_attributes, get_header_attrs,
3435
convert_angles)
3536
import pyspectral # testing that pyspectral is available # noqa: F401
@@ -59,7 +60,7 @@
5960

6061

6162
REFL_BANDS = ["M01", "M02", "M03", "M04", "M05", "M06", "M07", "M08",
62-
"M09"]
63+
"M09", "M10", "M11", "I01", "I02", "I03"]
6364

6465
MBAND_PPS = ["M05", "M07", "M09", "M10", "M11", "M12", "M14", "M15", "M16"]
6566

@@ -172,12 +173,83 @@ def set_header_and_band_attrs(scene, orbit_n=0):
172173
scene[band].attrs['sun_zenith_angle_correction_applied'] = 'True'
173174
return nimg
174175

176+
def midnight_scene(scene):
177+
"""Check if scene passes midnight."""
178+
start_date = scene["M05"].attrs["start_time"].strftime("%Y%m%d")
179+
end_date = scene["M05"].attrs["end_time"].strftime("%Y%m%d")
180+
if start_date == end_date:
181+
return False
182+
return True
183+
184+
185+
def get_midnight_line_nr(scene):
186+
"""Find midnight_line, start_time and new end_time."""
187+
start_date = scene["M05"].attrs["start_time"].strftime("%Y-%m-%d")
188+
end_date = scene["M05"].attrs["end_time"].strftime("%Y-%m-%d")
189+
start_fine_search = len(scene['scanline_timestamps']) - 1 # As default start the fine search from end of time array
190+
for ind in range(0, len(scene['scanline_timestamps']), 100):
191+
# Search from the beginning in large chunks (100) and break when we
192+
# pass midnight.
193+
dt_obj = dt64_to_datetime(scene['scanline_timestamps'].values[:][ind])
194+
date_linei = dt_obj.strftime("%Y-%m-%d")
195+
if date_linei == end_date:
196+
# We just passed midnight stop and search backwards for exact line.
197+
start_fine_search = ind
198+
break
199+
for indj in range(start_fine_search, start_fine_search - 100, -1):
200+
# Midnight is in one of the previous 100 lines.
201+
dt_obj = dt64_to_datetime(scene['scanline_timestamps'].values[:][indj])
202+
date_linei = dt_obj.strftime("%Y-%m-%d")
203+
if date_linei == start_date:
204+
# We just passed midnight this is the last line for previous day.
205+
midnight_linenr = indj
206+
break
207+
return midnight_linenr
208+
209+
210+
211+
def set_exact_time_and_crop(scene, start_line, end_line, time_key='scanline_timestamps'):
212+
"""Crop datasets and update start_time end_time objects."""
213+
if start_line is None:
214+
start_line = 0
215+
if end_line is None:
216+
end_line = len(scene[time_key]) - 1
217+
start_time_dt64 = scene[time_key].values[start_line]
218+
end_time_dt64 = scene[time_key].values[end_line]
219+
start_time = dt64_to_datetime(start_time_dt64)
220+
end_time = dt64_to_datetime(end_time_dt64)
221+
for ds in BANDNAMES + ANGLE_NAMES + ['latitude', 'longitude', 'scanline_timestamps']:
222+
if ds in scene and 'nscn' in scene[ds].dims:
223+
scene[ds] = scene[ds].isel(nscn=slice(start_line, end_line + 1))
224+
try:
225+
# Update scene attributes to get the filenames right
226+
scene[ds].attrs['start_time'] = start_time
227+
scene[ds].attrs['end_time'] = end_time
228+
except TypeError:
229+
pass
230+
if start_time_dt64 != scene[time_key].values[0]:
231+
raise ValueError
232+
if end_time_dt64 != scene[time_key].values[-1]:
233+
raise ValueError
234+
235+
def split_scene_at_midnight(scene):
236+
"""Split scenes at midnight."""
237+
if midnight_scene(scene):
238+
midnight_linenr = get_midnight_line_nr(scene)
239+
scene1 = scene.copy()
240+
scene2 = scene.copy()
241+
set_exact_time_and_crop(scene1, None, midnight_linenr)
242+
set_exact_time_and_crop(scene2, midnight_linenr + 1, None)
243+
return [scene1, scene2]
244+
return [scene]
245+
175246

176247
def process_one_scene(scene_files, out_path, engine='h5netcdf',
177-
all_channels=False, pps_channels=False, orbit_n=0, as_noaa19=False, avhrr_channels=False):
248+
all_channels=False, pps_channels=False, orbit_n=0, as_noaa19=False, avhrr_channels=False,
249+
split_files_at_midnight=True):
178250
"""Make level 1c files in PPS-format."""
179251
tic = time.time()
180-
scn_ = Scene(
252+
scn_in = Scene(
181253
reader='viirs_vgac_l1c_nc',
182254
filenames=scene_files)
183255

@@ -192,41 +264,46 @@ def process_one_scene(scene_files, out_path, engine='h5netcdf',
192264
if avhrr_channels:
193265
MY_MBAND = MBAND_AVHRR
194266

195-
scn_.load(MY_MBAND
196-
+ ANGLE_NAMES
197-
# + ['M12_LUT', 'M13_LUT', 'M15_LUT', 'M16_LUT']
198-
+ ['latitude', 'longitude', 'scanline_timestamps'])
199-
200-
# one ir channel
201-
irch = scn_['M15']
202-
203-
# Set header and band attributes
204-
set_header_and_band_attrs(scn_, orbit_n=orbit_n)
205-
206-
# Rename longitude, latitude to lon, lat.
207-
rename_latitude_longitude(scn_)
208-
209-
# Convert angles to PPS
210-
convert_angles(scn_, delete_azimuth=False)
211-
update_angle_attributes(scn_, irch)
212-
213-
# Adjust to noaa19 with sbafs from KG
214-
sensor = "viirs"
215-
if as_noaa19:
216-
sensor = "avhrr"
217-
convert_to_noaa19(scn_)
218-
219-
filename = compose_filename(scn_, out_path, instrument=sensor, band=irch)
220-
encoding = get_encoding_viirs(scn_)
221-
222-
scn_.save_datasets(writer='cf',
223-
filename=filename,
224-
header_attrs=get_header_attrs(scn_, band=irch, sensor=sensor),
225-
engine=engine,
226-
include_lonlats=False,
227-
flatten_attrs=True,
228-
encoding=encoding)
229-
print("Saved file {:s} after {:3.1f} seconds".format(
230-
os.path.basename(filename),
231-
time.time()-tic))
232-
return filename
267+
scn_in.load(MY_MBAND
268+
+ ANGLE_NAMES
269+
# + ['M12_LUT', 'M13_LUT', 'M15_LUT', 'M16_LUT']
270+
+ ['latitude', 'longitude', 'scanline_timestamps'])
271+
if split_files_at_midnight:
272+
scenes = split_scene_at_midnight(scn_in)
273+
else:
274+
scenes = [scn_in]
275+
filenames = []
276+
for scn_ in scenes:
277+
# one ir channel
278+
irch = scn_['M15']
279+
280+
# Set header and band attributes
281+
set_header_and_band_attrs(scn_, orbit_n=orbit_n)
282+
283+
# Rename longitude, latitude to lon, lat.
284+
rename_latitude_longitude(scn_)
285+
286+
# Convert angles to PPS
287+
convert_angles(scn_, delete_azimuth=False)
288+
update_angle_attributes(scn_, irch)
289+
# Adjust to noaa19 with sbafs from KG
290+
sensor = "viirs"
291+
if as_noaa19:
292+
sensor = "avhrr"
293+
convert_to_noaa19(scn_)
294+
295+
filename = compose_filename(scn_, out_path, instrument=sensor, band=irch)
296+
encoding = get_encoding_viirs(scn_)
297+
298+
scn_.save_datasets(writer='cf',
299+
filename=filename,
300+
header_attrs=get_header_attrs(scn_, band=irch, sensor=sensor),
301+
engine=engine,
302+
include_lonlats=False,
303+
flatten_attrs=True,
304+
encoding=encoding)
305+
print("Saved file {:s} after {:3.1f} seconds".format(
306+
os.path.basename(filename),
307+
time.time()-tic))
308+
filenames.append(filename)
309+
return filenames

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
data_files=[],
7171
zip_safe=False,
7272
use_scm_version=True,
73-
python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*',
73+
python_requires='>=3.7',
7474
install_requires=requires,
7575
test_suite='level1c4pps.tests.suite',
7676
)

0 commit comments

Comments
 (0)