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

Collocation of emep-mscw output with gridded data in a projection #1274

Merged
merged 9 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
37 changes: 28 additions & 9 deletions pyaerocom/colocation/colocation_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,10 +681,12 @@
"""
if filter_name is None:
filter_name = const.DEFAULT_REG_FILTER
try:
data.check_dimcoords_tseries()
except DimensionOrderError:
data.reorder_dimensions_tseries()
if data.proj_info is None:
# reordering only implemented if no projection
lewisblake marked this conversation as resolved.
Show resolved Hide resolved
try:
data.check_dimcoords_tseries()
except DimensionOrderError:
data.reorder_dimensions_tseries()

Check warning on line 689 in pyaerocom/colocation/colocation_utils.py

View check run for this annotation

Codecov / codecov/patch

pyaerocom/colocation/colocation_utils.py#L688-L689

Added lines #L688 - L689 were not covered by tests

var, var_aerocom = resolve_var_name(data)
if var_ref is None:
Expand Down Expand Up @@ -747,12 +749,28 @@

latitude = data.latitude.points
longitude = data.longitude.points
lat_range = [np.min(latitude), np.max(latitude)]
lon_range = [np.min(longitude), np.max(longitude)]
# use only sites that are within model domain
if data.proj_info is None:
lat_range = [np.min(latitude), np.max(latitude)]
lon_range = [np.min(longitude), np.max(longitude)]
# use only sites that are within model domain

# filter_by_meta wipes is_vertical_profile
data_ref = data_ref.filter_by_meta(latitude=lat_range, longitude=lon_range)
# filter_by_meta wipes is_vertical_profile
data_ref = data_ref.filter_by_meta(latitude=lat_range, longitude=lon_range)
else:
# gridded data with projection,
# add x/y information to ungridded
for coord in data.cube.dim_coords:
if coord.var_name == data.proj_info.x_axis:
vals = coord.points
xrange = (np.min(vals), np.max(vals))
if coord.var_name == data.proj_info.y_axis:
vals = coord.points
yrange = (np.min(vals), np.max(vals))
if xrange is None or yrange is None:
raise VariableDefinitionError(

Check warning on line 770 in pyaerocom/colocation/colocation_utils.py

View check run for this annotation

Codecov / codecov/patch

pyaerocom/colocation/colocation_utils.py#L770

Added line #L770 was not covered by tests
f"x/y axis not found in cube: {data.proj_info.x_axis}, {data.proj_info.y_axis}"
)
data_ref = data_ref.filter_by_projection(data.proj_info.to_proj, xrange, yrange)

# get timeseries from all stations in provided time resolution
# (time resampling is done below in main loop)
Expand All @@ -774,6 +792,7 @@
f"Variable {var_ref} is not available in specified time interval ({start}-{stop})"
)

# to read
grid_stat_data = data.to_time_series(longitude=ungridded_lons, latitude=ungridded_lats)

pd_freq = col_tst.to_pandas_freq()
Expand Down
61 changes: 47 additions & 14 deletions pyaerocom/griddeddata.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
)
from pyaerocom.helpers_landsea_masks import load_region_mask_iris
from pyaerocom.mathutils import estimate_value_range, exponent
from pyaerocom.projection_information import ProjectionInformation
from pyaerocom.region import Region
from pyaerocom.stationdata import StationData
from pyaerocom.time_config import IRIS_AGGREGATORS, TS_TYPE_TO_NUMPY_FREQ
Expand Down Expand Up @@ -130,10 +131,17 @@
concatenated=False,
region=None,
reader=None,
proj_info=None,
)

def __init__(
self, input=None, var_name=None, check_unit=True, convert_unit_on_init=True, **meta
self,
input=None,
var_name=None,
check_unit=True,
convert_unit_on_init=True,
proj_info: ProjectionInformation | None = None,
**meta,
):
if input is None:
input = iris.cube.Cube([])
Expand All @@ -152,6 +160,8 @@
self._coord_var_names = None
self._coord_standard_names = None
self._coord_long_names = None
# projection information
meta.update({"proj_info": proj_info})

if input:
self.load_input(input, var_name)
Expand Down Expand Up @@ -195,6 +205,10 @@
f"No default access available for variable {self.var_name}"
)

@property
def proj_info(self) -> ProjectionInformation:
return self.metadata["proj_info"]

@property
def ts_type(self):
"""
Expand Down Expand Up @@ -1130,10 +1144,11 @@
collapse_scalar = coords.pop("collapse_scalar")
else:
collapse_scalar = True
try:
self.check_dimcoords_tseries()
except DimensionOrderError:
self.reorder_dimensions_tseries()
if self.proj_info is None:
try:
self.check_dimcoords_tseries()
except DimensionOrderError:
self.reorder_dimensions_tseries()

Check warning on line 1151 in pyaerocom/griddeddata.py

View check run for this annotation

Codecov / codecov/patch

pyaerocom/griddeddata.py#L1150-L1151

Added lines #L1150 - L1151 were not covered by tests
pinfo = False
if np.prod(self.shape) > 5913000: # (shape of 2x2 deg, daily data)
pinfo = True
Expand Down Expand Up @@ -1176,10 +1191,11 @@
)

def _to_time_series_xarray(self, scheme="nearest", add_meta=None, ts_type=None, **coords):
try:
self.check_dimcoords_tseries()
except DimensionOrderError:
self.reorder_dimensions_tseries()
if self.proj_info is None:
try:
self.check_dimcoords_tseries()
except DimensionOrderError:
self.reorder_dimensions_tseries()

Check warning on line 1198 in pyaerocom/griddeddata.py

View check run for this annotation

Codecov / codecov/patch

pyaerocom/griddeddata.py#L1197-L1198

Added lines #L1197 - L1198 were not covered by tests

arr = self.to_xarray()

Expand All @@ -1198,7 +1214,21 @@
lon = vals
if lat is None or lon is None:
raise ValueError("Please provide latitude and longitude coords")
subset = extract_latlon_dataarray(arr, lat, lon, method=scheme, new_index_name="latlon")
if self.proj_info is None:
subset = extract_latlon_dataarray(
arr, lat, lon, method=scheme, new_index_name="latlon"
)
else:
x, y = self.proj_info.to_proj(lat, lon)
subset = extract_latlon_dataarray(
arr,
lon=x,
lat=y,
lon_dimname=self.proj_info.x_axis,
lat_dimname=self.proj_info.y_axis,
method=scheme,
new_index_name="latlon",
)

lat_id = subset.attrs["lat_dimname"]
lon_id = subset.attrs["lon_dimname"]
Expand All @@ -1219,8 +1249,11 @@
result = []
subset = subset.compute()
data_np = subset.data
lats = subset[lat_id].data
lons = subset[lon_id].data
if self.proj_info is None:
lats = subset[lat_id].data
lons = subset[lon_id].data
else:
lats, lons = self.proj_info.to_latlon(subset[lon_id].data, subset[lat_id].data)
for sidx in range(subset.shape[-1]):
data = StationData(
latitude=lats[sidx],
Expand Down Expand Up @@ -2603,8 +2636,8 @@
f"Skipping assignment of var_name from metadata in GriddedData, "
f"since attr. needs to be str and is {val}"
)
continue
self._grid.attributes[key] = val
else:
self._grid.attributes[key] = val

def delete_all_coords(self, inplace=True):
"""Deletes all coordinates (dimension + auxiliary) in this object"""
Expand Down
1 change: 1 addition & 0 deletions pyaerocom/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ def extract_latlon_dataarray(
check_domain=True,
):
"""Extract individual lat / lon coordinates from `DataArray`
lon/lat can also be x/y coordinates if the `DataArray` has only projected axes.

Parameters
----------
Expand Down
20 changes: 14 additions & 6 deletions pyaerocom/io/mscw_ctm/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pyaerocom import const
from pyaerocom.exceptions import VarNotAvailableError
from pyaerocom.griddeddata import GriddedData
from pyaerocom.projection_information import ProjectionInformation
from pyaerocom.units_helpers import UALIASES

from .additional_variables import (
Expand Down Expand Up @@ -599,19 +600,21 @@ def _compute_var(self, var_name_aerocom, ts_type):

Returns
-------
GriddedData
xarray.DataArray
loaded data object

"""

temp_arrs = []
req = self.AUX_REQUIRES[var_name_aerocom]
aux_func = self.AUX_FUNS[var_name_aerocom]
logger.info(f"computing {var_name_aerocom} from {req} using {aux_func}")
proj_info = None
for aux_var in self.AUX_REQUIRES[var_name_aerocom]:
arr = self._load_var(aux_var, ts_type)
arr, proj_info = self._load_var(aux_var, ts_type)
temp_arrs.append(arr)

return aux_func(*temp_arrs)
return aux_func(*temp_arrs), proj_info

def _load_var(self, var_name_aerocom, ts_type):
"""
Expand All @@ -636,6 +639,8 @@ def _load_var(self, var_name_aerocom, ts_type):
-------
xarray.DataArray
loaded data
ProjectionInformation
projection of variable

"""
if var_name_aerocom in self.var_map: # can be read
Expand Down Expand Up @@ -677,7 +682,7 @@ def read_var(self, var_name, ts_type=None, **kwargs):

ts_type = self.ts_type

arr = self._load_var(var_name_aerocom, ts_type)
arr, proj_info = self._load_var(var_name_aerocom, ts_type)
if arr.units in UALIASES:
arr.attrs["units"] = UALIASES[arr.units]
try:
Expand All @@ -693,6 +698,7 @@ def read_var(self, var_name, ts_type=None, **kwargs):
ts_type=ts_type,
check_unit=True,
convert_unit_on_init=True,
proj_info=proj_info,
)

#!obsolete
Expand Down Expand Up @@ -732,14 +738,16 @@ def _read_var_from_file(self, var_name_aerocom, ts_type):
-------
xarray.DataArray
loaded data
ProjectionInformation
projection of variable

"""
emep_var = self.var_map[var_name_aerocom]

try:
filedata = self.filedata
data = filedata[emep_var]

proj_info = ProjectionInformation.from_xarray(filedata, emep_var)
except KeyError:
raise VarNotAvailableError(
f"{var_name_aerocom} ({emep_var}) not available in {self.filename}"
Expand All @@ -749,7 +757,7 @@ def _read_var_from_file(self, var_name_aerocom, ts_type):
data.time.attrs["standard_name"] = "time"
prefix = emep_var.split("_")[0]
data.attrs["units"] = self.preprocess_units(data.units, prefix)
return data
return data, proj_info

@staticmethod
def preprocess_units(units, prefix):
Expand Down
89 changes: 89 additions & 0 deletions pyaerocom/projection_information.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import xarray
from pyproj import CRS, transformer


class ProjectionInformationException(Exception):
pass


class ProjectionInformation:
def __init__(self):
self._crs = None
self._x_axis = None
self._y_axis = None
self._units = None

@property
def x_axis(self):
return self._x_axis

@property
def y_axis(self):
return self._y_axis

def to_proj(self, latitude, longitude):
"""convert latitude and longitude to x and y

:param latitude: latitude values
:param longitude: longitude values
:return: tuple of x and y coordinates
"""
trans = transformer.Transformer.from_crs(4326, self._crs)
return trans.transform(latitude, longitude)

def to_latlon(self, x, y):
"""convert x and y to latitude and longitude

:param x: x values
:param y: y values
:return: tuple of latitude and longitude coordinates
"""
trans = transformer.Transformer.from_crs(self._crs, 4326)
return trans.transform(x, y)

@staticmethod
def from_xarray(ds: xarray.Dataset, var: str):
"""initialize the ProjectionInformation from an xarray variable

returns None if no projection exists for the variable or ProjectionInformation
"""
da = ds[var]
if not "grid_mapping" in da.attrs:
return None
pi = ProjectionInformation()
pi._crs = CRS.from_cf(ds[da.grid_mapping].attrs)

for c in da.coords:
if len(da.coords[c].dims) != 1:
continue

Check warning on line 58 in pyaerocom/projection_information.py

View check run for this annotation

Codecov / codecov/patch

pyaerocom/projection_information.py#L58

Added line #L58 was not covered by tests
if "standard_name" in da.coords[c].attrs:
if da.coords[c].standard_name in (
"longitude",
"grid_longitude",
"projection_x_coordinate",
):
pi._x_axis = c
break
if "axis" in da.coords[c].attrs:
if da.coords[c].axis in ("x", "X"):
pi._x_axis = c
break

Check warning on line 70 in pyaerocom/projection_information.py

View check run for this annotation

Codecov / codecov/patch

pyaerocom/projection_information.py#L67-L70

Added lines #L67 - L70 were not covered by tests
for c in da.coords:
if len(da.coords[c].dims) != 1:
continue

Check warning on line 73 in pyaerocom/projection_information.py

View check run for this annotation

Codecov / codecov/patch

pyaerocom/projection_information.py#L73

Added line #L73 was not covered by tests
if "standard_name" in da.coords[c].attrs:
if da.coords[c].standard_name in (
"latitude",
"grid_latitude",
"projection_y_coordinate",
):
pi._y_axis = c
break
if "axis" in da.coords[c].attrs:
if da.coords[c].axis in ("y", "Y"):
pi._y_axis = c
break

Check warning on line 85 in pyaerocom/projection_information.py

View check run for this annotation

Codecov / codecov/patch

pyaerocom/projection_information.py#L84-L85

Added lines #L84 - L85 were not covered by tests
if pi._x_axis is None or pi._y_axis is None:
raise ProjectionInformationException(f"no x or y axis found for variable '{var}'")

Check warning on line 87 in pyaerocom/projection_information.py

View check run for this annotation

Codecov / codecov/patch

pyaerocom/projection_information.py#L87

Added line #L87 was not covered by tests
pi._units = da.coords[pi._y_axis].units
return pi
3 changes: 2 additions & 1 deletion pyaerocom/sample_data_access/minimal_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
logger = logging.getLogger(__name__)

#: tarfile to download
TESTDATA_FILE = "testdata-minimal.tar.gz.20231116"
TESTDATA_FILE = "testdata-minimal.tar.gz.20240722"

minimal_dataset = pooch.create(
path=const.OUTPUTDIR, # ~/MyPyaerocom/
Expand All @@ -21,6 +21,7 @@
"testdata-minimal.tar.gz.20231017": "md5:705d91e01ca7647b4c93dfe67def661f",
"testdata-minimal.tar.gz.20231019": "md5:f8912ee83d6749fb2a9b1eda1d664ca2",
"testdata-minimal.tar.gz.20231116": "md5:5da747f6596817295ba7affe3402b722",
"testdata-minimal.tar.gz.20240722": "md5:7d933901c6d273d012f132c60df086cc",
},
)

Expand Down
Loading