Skip to content

Commit

Permalink
reverse
Browse files Browse the repository at this point in the history
  • Loading branch information
yukaribbba committed May 15, 2024
1 parent 3e61dbe commit 809107e
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 803 deletions.
57 changes: 30 additions & 27 deletions satpy/etc/readers/msi_safe.yaml
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
reader:
name: msi_safe
short_name: MSI SAFE L1C
long_name: Sentinel-2 A and B MSI L1C data in SAFE format
description: SAFE Reader for MSI L1C data (Sentinel-2)
short_name: MSI SAFE
long_name: Sentinel-2 A and B MSI data in SAFE format, supporting L1C format only.
description: SAFE Reader for MSI data (Sentinel-2)
status: Nominal
supports_fsspec: false
sensors: [msi]
default_channels: []
reader: !!python/name:satpy.readers.yaml_reader.FileYAMLReader

file_types:
l1c_safe_granule:
safe_granule_l1c:
file_reader: !!python/name:satpy.readers.msi_safe.SAFEMSIL1C
file_patterns: ['{fmission_id:3s}_MSI{process_level:3s}_{observation_time:%Y%m%dT%H%M%S}_N{fprocessing_baseline_number:4d}_R{relative_orbit_number:3d}_T{dtile_number:5s}_{dproduct_discriminator:%Y%m%dT%H%M%S}.SAFE/GRANULE/L1C_T{gtile_number:5s}_A{absolute_orbit_number:6d}_{gfile_discriminator:%Y%m%dT%H%M%S}/IMG_DATA/T{tile_number:5s}_{file_discriminator:%Y%m%dT%H%M%S}_{band_name:3s}.jp2']
requires: [l1c_safe_metadata, l1c_safe_tile_metadata]
l1c_safe_tile_metadata:
file_patterns: ['{fmission_id:3s}_MSI{proclevel:3s}_{observation_time:%Y%m%dT%H%M%S}_N{fprocessing_baseline_number:4d}_R{relative_orbit_number:3d}_T{dtile_number:5s}_{dproduct_discriminator:%Y%m%dT%H%M%S}.SAFE/GRANULE/L1C_T{gtile_number:5s}_A{absolute_orbit_number:6d}_{gfile_discriminator:%Y%m%dT%H%M%S}/IMG_DATA/T{tile_number:5s}_{file_discriminator:%Y%m%dT%H%M%S}_{band_name:3s}.jp2']
requires: [safe_metadata, safe_tile_metadata]
safe_tile_metadata:
file_reader: !!python/name:satpy.readers.msi_safe.SAFEMSITileMDXML
file_patterns: ['{fmission_id:3s}_MSI{process_level:3s}_{observation_time:%Y%m%dT%H%M%S}_N{fprocessing_baseline_number:4d}_R{relative_orbit_number:3d}_T{dtile_number:5s}_{dproduct_discriminator:%Y%m%dT%H%M%S}.SAFE/GRANULE/L1C_T{gtile_number:5s}_A{absolute_orbit_number:6d}_{gfile_discriminator:%Y%m%dT%H%M%S}/MTD_TL.xml']
l1c_safe_metadata:
file_patterns: ['{fmission_id:3s}_MSI{proclevel:3s}_{observation_time:%Y%m%dT%H%M%S}_N{fprocessing_baseline_number:4d}_R{relative_orbit_number:3d}_T{dtile_number:5s}_{dproduct_discriminator:%Y%m%dT%H%M%S}.SAFE/GRANULE/L1C_T{gtile_number:5s}_A{absolute_orbit_number:6d}_{gfile_discriminator:%Y%m%dT%H%M%S}/MTD_TL.xml']
safe_metadata:
file_reader: !!python/name:satpy.readers.msi_safe.SAFEMSIMDXML
file_patterns: ['{fmission_id:3s}_MSI{process_level:3s}_{observation_time:%Y%m%dT%H%M%S}_N{fprocessing_baseline_number:4d}_R{relative_orbit_number:3d}_T{dtile_number:5s}_{dproduct_discriminator:%Y%m%dT%H%M%S}.SAFE/MTD_MSIL1C.xml']
file_patterns: ['{fmission_id:3s}_MSI{proclevel:3s}_{observation_time:%Y%m%dT%H%M%S}_N{fprocessing_baseline_number:4d}_R{relative_orbit_number:3d}_T{dtile_number:5s}_{dproduct_discriminator:%Y%m%dT%H%M%S}.SAFE/MTD_MSIL1C.xml']


datasets:

B01:
name: B01
sensor: msi
Expand All @@ -37,7 +39,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: l1c_safe_granule
file_type: safe_granule_l1c

B02:
name: B02
Expand All @@ -54,7 +56,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: l1c_safe_granule
file_type: safe_granule_l1c

B03:
name: B03
Expand All @@ -71,7 +73,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: l1c_safe_granule
file_type: safe_granule_l1c

B04:
name: B04
Expand All @@ -88,7 +90,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: l1c_safe_granule
file_type: safe_granule_l1c

B05:
name: B05
Expand All @@ -105,7 +107,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: l1c_safe_granule
file_type: safe_granule_l1c

B06:
name: B06
Expand All @@ -122,7 +124,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: l1c_safe_granule
file_type: safe_granule_l1c

B07:
name: B07
Expand All @@ -139,7 +141,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: l1c_safe_granule
file_type: safe_granule_l1c

B08:
name: B08
Expand All @@ -156,7 +158,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: l1c_safe_granule
file_type: safe_granule_l1c

B8A:
name: B8A
Expand All @@ -173,7 +175,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: l1c_safe_granule
file_type: safe_granule_l1c

B09:
name: B09
Expand All @@ -190,7 +192,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: l1c_safe_granule
file_type: safe_granule_l1c

B10:
name: B10
Expand All @@ -207,7 +209,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: l1c_safe_granule
file_type: safe_granule_l1c

B11:
name: B11
Expand All @@ -224,7 +226,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: l1c_safe_granule
file_type: safe_granule_l1c

B12:
name: B12
Expand All @@ -241,30 +243,31 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: l1c_safe_granule
file_type: safe_granule_l1c


solar_zenith_angle:
name: solar_zenith_angle
resolution: [10, 20, 60]
file_type: l1c_safe_tile_metadata
file_type: safe_tile_metadata
xml_tag: Sun_Angles_Grid/Zenith

solar_azimuth_angle:
name: solar_azimuth_angle
resolution: [10, 20, 60]
file_type: l1c_safe_tile_metadata
file_type: safe_tile_metadata
xml_tag: Sun_Angles_Grid/Azimuth

satellite_azimuth_angle:
name: satellite_azimuth_angle
resolution: [10, 20, 60]
file_type: l1c_safe_tile_metadata
file_type: safe_tile_metadata
xml_tag: Viewing_Incidence_Angles_Grids
xml_item: Azimuth

satellite_zenith_angle:
name: satellite_zenith_angle
resolution: [10, 20, 60]
file_type: l1c_safe_tile_metadata
file_type: safe_tile_metadata
xml_tag: Viewing_Incidence_Angles_Grids
xml_item: Zenith
66 changes: 19 additions & 47 deletions satpy/readers/msi_safe.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,14 @@
reader_kwargs={'mask_saturated': False})
scene.load(['B01'])
L1C/L2A format description for the files read here:
L1C format description for the files read here:
https://sentinels.copernicus.eu/documents/247904/685211/S2-PDGS-TAS-DI-PSD-V14.9.pdf/3d3b6c9c-4334-dcc4-3aa7-f7c0deffbaf7?t=1643013091529
Please note: for L2A datasets, the band name has been fixed with a "_L2A" suffix. Do not change it in the YAML file or
the reader can't recogonize it and nothing will be loaded.
https://sentinels.copernicus.eu/documents/247904/0/Sentinel-2-product-specifications-document-V14-9.pdf/
"""

import logging
from datetime import datetime

import dask.array as da
import defusedxml.ElementTree as ET
Expand Down Expand Up @@ -66,28 +64,21 @@ def __init__(self, filename, filename_info, filetype_info, mda, tile_mda, mask_s
super(SAFEMSIL1C, self).__init__(filename, filename_info,
filetype_info)
del mask_saturated
self._start_time = filename_info["observation_time"]
self._end_time = filename_info["observation_time"]
self._channel = filename_info["band_name"]
self.process_level = filename_info["process_level"]
self._tile_mda = tile_mda
self._mda = mda
self.platform_name = PLATFORMS[filename_info["fmission_id"]]

self._start_time = self._tile_mda.start_time()
self._end_time = filename_info["observation_time"]

def get_dataset(self, key, info):
"""Load a dataset."""
if self.process_level == "L1C":
if self._channel != key["name"]:
return
else:
if self._channel + "_L2A" != key["name"]:
return
if self._channel != key["name"]:
return

logger.debug("Reading %s.", key["name"])

proj = self._read_from_file(key)
if proj is None:
return
proj.attrs = info.copy()
proj.attrs["units"] = "%"
proj.attrs["platform_name"] = self.platform_name
Expand All @@ -102,8 +93,6 @@ def _read_from_file(self, key):
return self._mda.calibrate_to_radiances(proj, self._channel)
if key["calibration"] == "counts":
return self._mda._sanitize_data(proj)
if key["calibration"] in ["aerosol_thickness", "water_vapor"]:
return self._mda.calibrate_to_atmospheric(proj, self._channel)

@property
def start_time(self):
Expand All @@ -117,13 +106,8 @@ def end_time(self):

def get_area_def(self, dsid):
"""Get the area def."""
if self.process_level == "L1C":
if self._channel != dsid["name"]:
return
else:
if self._channel + "_L2A" != dsid["name"]:
return

if self._channel != dsid["name"]:
return
return self._tile_mda.get_area_def(dsid)


Expand All @@ -137,7 +121,6 @@ def __init__(self, filename, filename_info, filetype_info, mask_saturated=True):
self._end_time = filename_info["observation_time"]
self.root = ET.parse(self.filename)
self.tile = filename_info["dtile_number"]
self.process_level = filename_info["process_level"]
self.platform_name = PLATFORMS[filename_info["fmission_id"]]
self.mask_saturated = mask_saturated
import bottleneck # noqa
Expand All @@ -159,23 +142,10 @@ class SAFEMSIMDXML(SAFEMSIXMLMetadata):

def calibrate_to_reflectances(self, data, band_name):
"""Calibrate *data* using the radiometric information for the metadata."""
quantification = int(self.root.find(".//QUANTIFICATION_VALUE").text) if self.process_level == "L1C" else \
int(self.root.find(".//BOA_QUANTIFICATION_VALUE").text)
quantification = int(self.root.find(".//QUANTIFICATION_VALUE").text)
data = self._sanitize_data(data)
return (data + self.band_offset(band_name)) / quantification * 100

def calibrate_to_atmospheric(self, data, band_name):
"""Calibrate L2A AOT/WVP product."""
atmospheric_bands = ["AOT", "WVP"]
if self.process_level == "L1C":
return
elif self.process_level == "L2A" and band_name not in atmospheric_bands:
return

quantification = float(self.root.find(f".//{band_name}_QUANTIFICATION_VALUE").text)
data = self._sanitize_data(data)
return data / quantification

def _sanitize_data(self, data):
data = data.where(data != self.no_data)
if self.mask_saturated:
Expand Down Expand Up @@ -204,8 +174,7 @@ def band_indices(self):
@cached_property
def band_offsets(self):
"""Get the band offsets from the metadata."""
offsets = self.root.find(".//Radiometric_Offset_List") if self.process_level == "L1C" else \
self.root.find(".//BOA_ADD_OFFSET_VALUES_LIST")
offsets = self.root.find(".//Radiometric_Offset_List")
if offsets is not None:
band_offsets = {int(off.attrib["band_id"]): float(off.text) for off in offsets}
else:
Expand Down Expand Up @@ -302,6 +271,11 @@ def _shape(self, resolution):
cols = int(self.geocoding.find('Size[@resolution="' + str(resolution) + '"]/NCOLS').text)
return cols, rows

def start_time(self):
"""Get the observation time from the tile metadata."""
timestr = self.root.find(".//SENSING_TIME").text
return datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S.%fZ")

@staticmethod
def _do_interp(minterp, xcoord, ycoord):
interp_points2 = np.vstack((ycoord.ravel(), xcoord.ravel()))
Expand All @@ -328,11 +302,9 @@ def interpolate_angles(self, angles, resolution):
def _get_coarse_dataset(self, key, info):
"""Get the coarse dataset refered to by `key` from the XML data."""
angles = self.root.find(".//Tile_Angles")
if key["name"] in ["solar_zenith_angle", "solar_azimuth_angle",
"solar_zenith_angle_l2a", "solar_azimuth_angle_l2a"]:
if key["name"] in ["solar_zenith_angle", "solar_azimuth_angle"]:
angles = self._get_solar_angles(angles, info)
elif key["name"] in ["satellite_zenith_angle", "satellite_azimuth_angle",
"satellite_zenith_angle_l2a", "satellite_azimuth_angle_l2a"]:
elif key["name"] in ["satellite_zenith_angle", "satellite_azimuth_angle"]:
angles = self._get_satellite_angles(angles, info)
else:
angles = None
Expand Down

0 comments on commit 809107e

Please sign in to comment.