Skip to content

Commit 5e4c34b

Browse files
authored
Add historic IASI L2 format (#10)
* - Update python example - Update MetopVariable to work with any AbstractArray - Add IASI L2 V10 format - Expose IASI L2 GIARD variables * - Add CHANGELOG - update IASI information - update documentation page * Update python docs.
1 parent 64c08c5 commit 5e4c34b

19 files changed

+463
-87
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Changelog
2+
3+
## Unreleased
4+
- No items yet.
5+
6+
## v0.1
7+
- (**BREAKING**) Initial release with support for IASI and ASCAT formats.

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "MetopDatasets"
22
uuid = "0c26954c-4046-4b98-a13c-f9377ca4b9b7"
33
authors = ["lupemba <simon.koklupemba@eumetsat.int> and contributors"]
4-
version = "0.0.7"
4+
version = "0.1.0"
55

66
[deps]
77
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"

docs/src/IASI.md

+2-11
Original file line numberDiff line numberDiff line change
@@ -246,17 +246,8 @@ longitude, latitude = let
246246
end
247247

248248
# Read the pressure levels
249-
temp_pressure_levels, hum_pressure_levels = let
250-
giard = MetopDatasets.read_first_record(ds, MetopDatasets.GIADR_IASI_SND_02_V11)
251-
252-
scale_factor_temp = MetopDatasets.get_scale_factor(MetopDatasets.GIADR_IASI_SND_02_V11, :pressure_levels_temp)
253-
temp_level = giard.pressure_levels_temp/10^scale_factor_temp
254-
255-
scale_factor_humidity = MetopDatasets.get_scale_factor(MetopDatasets.GIADR_IASI_SND_02_V11, :pressure_levels_humidity)
256-
humidity_level = giard.pressure_levels_humidity/10^scale_factor_humidity
257-
258-
temp_level, humidity_level
259-
end
249+
temp_pressure_levels = cfvariable(ds, "pressure_levels_temp", maskingvalue = NaN)[:]
250+
hum_pressure_levels = cfvariable(ds, "pressure_levels_humidity", maskingvalue = NaN)[:]
260251

261252
# Plot figure
262253
fig = let

docs/src/index.md

+3-5
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ It is also possible to use MetopDatasets.jl from Python. See [Use with Python](@
1616
This section gives a very short overview of the core functionalities. The [MetopDataset](@ref) page is recommend for more information and more specific examples are given in the Example section e.g. [ASCAT](@ref). The [NCDatasets documentation](https://alexander-barth.github.io/NCDatasets.jl/stable/) is also a great resource for information on how to use the datasets.
1717

1818
### Installation
19-
MetopDatasets.jl can be installed via Pkg and the url to the GitHub repository.
19+
MetopDatasets.jl can be installed via Pkg.
2020

2121
```julia
2222
import Pkg
23-
Pkg.add(url="https://github.com/eumetsat/MetopDatasets.jl#main")
23+
Pkg.add("MetopDatasets")
2424
```
2525

2626
### Read data from a Metop Native binary file
@@ -361,6 +361,4 @@ end
361361
## Development status and versioning
362362
The package was previously named **MetopNative.jl** and was hosted on the [EUMETSAT GitLab](https://gitlab.eumetsat.int/eumetlab/cross-cutting-tools/MetopNative.jl).
363363

364-
This package is still in the early development phase and is not yet registered. The plan is to register the package once it is complete for ASCAT and IASI products. The package will not follow any specific version system before it is registered.
365-
366-
After registration, the aim will be to follow the [semantic versioning](https://semver.org/) system. Note that the package will start as 0.x.y to signal that breaking changes are to be expected. This is done to allow for more rapid development and because major dependencies like the [CommonDataModel.jl](https://github.com/JuliaGeo/CommonDataModel.jl) are not at the version 1.0 milestone yet. It is therefore recommended to use [Pkg environments](https://pkgdocs.julialang.org/v1/compatibility/) for projects with MetopDatasets.jl to handle comparability and ensure reproducibility. Please note that many of the "breaking changes" will be small and only affect specific use cases. This could for example be the correction of a single variable name. Any breaking change will be listed in the [release description](https://github.com/eumetsat/MetopDatasets.jl/releases).
364+
The aim is to follow the [semantic versioning](https://semver.org/) system. Note that the package is still 0.x.y to signal that breaking changes are to be expected. This is done to allow for more rapid development and because major dependencies like the [CommonDataModel.jl](https://github.com/JuliaGeo/CommonDataModel.jl) are not at the version 1.0 milestone yet. It is therefore recommended to use [Pkg environments](https://pkgdocs.julialang.org/v1/compatibility/) for projects with MetopDatasets.jl to handle comparability and ensure reproducibility. Please note that many of the "breaking changes" will be small and only affect specific use cases. This could for example be the correction of a single variable name. All breaking changes are marked in the [changelog](https://github.com/eumetsat/MetopDatasets.jl/blob/main/CHANGELOG.md).

docs/src/python.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ julia --version
1414
pip --version
1515
```
1616
This guide is tested with the following versions
17-
- Python 3.9.12
17+
- Python 3.12.8
1818
- julia version 1.11.1
19-
- pip 21.2.4
19+
- pip 24.2
2020
### Installing python packages
2121
We use pip to install `juliacall` and `numpy`. We need `juliacall` to interface with julia and `numpy` is just needed to demonstrate compatibility with numpy arrays.
2222

@@ -32,7 +32,7 @@ import juliacall
3232
# make separate module
3333
jl = juliacall.newmodule("MetopDatasetsPy")
3434
jl.seval("import Pkg")
35-
jl.Pkg.add(url="https://github.com/eumetsat/MetopDatasets.jl")
35+
jl.Pkg.add("MetopDatasets")
3636
```
3737

3838
### Example
@@ -48,7 +48,7 @@ jl.seval("using MetopDatasets")
4848
The dataset is simply read with `MetopDataset`. Only the metadata is read straight away. The variables can be read on demand.
4949
```python
5050
test_file = "/tcenas/home/lupemba/Documents/data/IASI_xxx_1C_M01_20240819103856Z_20240819104152Z_N_C_20240819112911Z"
51-
ds = jl.MetopDataset(test_file)
51+
ds = jl.MetopDataset(test_file, maskingvalue=float('nan'))
5252
```
5353
The dataset has a method equivalent to `__repr__` so the structure of the dataset can be shown easily. The julia `keys` function can be used to only list variable names.
5454
```python

src/Instruments/IASI/DiskArrayExtra.jl

-5
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,3 @@ function DiskArrays.readblock!(disk_array::IasiWaveNumberDiskArray, aout,
9393
aout .= sample_width .* (number_of_first_sample .+ i_channel .- 2)
9494
return nothing
9595
end
96-
97-
function CDM.dimnames(disk_array::IasiWaveNumberDiskArray)
98-
spectrum_dim = get_field_dimensions(disk_array.record_type, :gs1cspect)[1]
99-
return [spectrum_dim, RECORD_DIM_NAME]
100-
end

src/Instruments/IASI/IASI_dimensions.jl

+36-7
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,29 @@ end
7575

7676
############ IASI_SND_02 ################
7777

78+
function _get_fixed_dimensions(T::Type{IASI_SND_02_V11})
79+
dimensions_dict = Dict(
80+
"lat_lon" => 2,
81+
"cloud_formations" => 3,
82+
"solar_sat_zenith_azimuth" => 4,
83+
"xtrack_sounder_pixels" => 120
84+
)
85+
return dimensions_dict
86+
end
87+
88+
function _get_fixed_dimensions(T::Type{IASI_SND_02_V10})
89+
dimensions_dict = Dict(
90+
"lat_lon" => 2,
91+
"cloud_formations" => 3,
92+
"solar_sat_zenith_azimuth" => 4,
93+
"xtrack_sounder_pixels" => 120,
94+
IASI_L2_V10_03_PRESSURE_DIM => 2,
95+
"n_surface_temperatures" => 2,
96+
"error_data_dims" => 2
97+
)
98+
return dimensions_dict
99+
end
100+
78101
function get_dimensions(T::Type{<:IASI_SND_02},
79102
data_record_layouts::Vector{<:RecordLayout})::Dict{String, <:Integer}
80103
dimensions_dict = Dict{String, Integer}()
@@ -99,29 +122,35 @@ function get_dimensions(T::Type{<:IASI_SND_02},
99122
end
100123
end
101124

102-
# default others
103-
dimensions_dict["lat_lon"] = 2 #The coordinates have opposite order of L1C!
104-
dimensions_dict["cloud_formations"] = 3
105-
dimensions_dict["solar_sat_zenith_azimuth"] = 4
106-
dimensions_dict["xtrack_sounder_pixels"] = 120
125+
merge!(dimensions_dict, _get_fixed_dimensions(T))
107126

108127
return dimensions_dict
109128
end
110129

111130
function get_field_dimensions(
112-
T::Type{<:IASI_SND_02}, layout::FlexibleRecordLayout, field_name::Symbol)
131+
T::Type{<:IASI_SND_02}, field_name::Symbol)
113132
res = String[]
114133

115134
if !(fieldtype(T, field_name) <: Array)
116135
return res
117136
end
118137

119-
dimension_dict = get_dimensions(T, [layout])
138+
dimension_dict = _get_fixed_dimensions(T)
120139
array_size = _get_array_size(T, field_name)
121140

122141
for d in array_size
123142
if d isa Symbol
124143
push!(res, string(d))
144+
elseif T <: IASI_SND_02_V10 && d == 2
145+
if field_name == :pressure_levels_ozone
146+
push!(res, IASI_L2_V10_03_PRESSURE_DIM)
147+
elseif field_name == :surface_temperature
148+
push!(res, "n_surface_temperatures")
149+
elseif field_name == :data_sizes
150+
push!(res, "error_data_dims")
151+
else
152+
push!(res, "lat_lon")
153+
end
125154
else
126155
names = [dim_name for (dim_name, dim_val) in dimension_dict if dim_val == d]
127156
push!(res, string(first(names)))

src/Instruments/IASI/IASI_giadr_02.jl

+56-4
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,39 @@
22
# License: MIT
33

44
# Add IASI Level 2 meta data record.
5-
GIADR_IASI_SND_02_V11_format = joinpath(@__DIR__, "csv_formats/GIADR_IASI_SND_02_V11.csv")
5+
const IASI_L2_V10_03_PRESSURE_DIM = "n_o3_profiles"
66

77
abstract type GIADR_IASI_SND_02 <: GlobalInternalAuxillary end
88

9+
GIADR_IASI_SND_02_V11_format = joinpath(@__DIR__, "csv_formats/GIADR_IASI_SND_02_V11.csv")
10+
GIADR_IASI_SND_02_V10_format = joinpath(@__DIR__, "csv_formats/GIADR_IASI_SND_02_V10.csv")
11+
912
eval(record_struct_expression(GIADR_IASI_SND_02_V11_format, GIADR_IASI_SND_02))
13+
eval(record_struct_expression(GIADR_IASI_SND_02_V10_format, GIADR_IASI_SND_02))
1014

1115
get_instrument_subclass(::Type{<:GIADR_IASI_SND_02}) = 1
1216

13-
function get_flexible_dim_fields(::Type{<:GIADR_IASI_SND_02})
17+
function get_giard_varnames(::Type{GIADR_IASI_SND_02_V11})
18+
return (
19+
:pressure_levels_temp,
20+
:pressure_levels_humidity,
21+
:pressure_levels_ozone,
22+
:surface_emissivity_wavelengths,
23+
:forli_layer_heights_co,
24+
:forli_layer_heights_hno3,
25+
:forli_layer_heights_o3,
26+
:brescia_altitudes_so2)
27+
end
28+
29+
function get_giard_varnames(::Type{GIADR_IASI_SND_02_V10})
30+
return (
31+
:pressure_levels_temp,
32+
:pressure_levels_ozone,
33+
:pressure_levels_humidity,
34+
:surface_emissivity_wavelengths)
35+
end
36+
37+
function get_flexible_dim_fields(::Type{GIADR_IASI_SND_02_V11})
1438
return Dict(
1539
:num_temperature_pcs => :NPCT,
1640
:num_pressure_levels_temp => :NLT,
@@ -25,7 +49,15 @@ function get_flexible_dim_fields(::Type{<:GIADR_IASI_SND_02})
2549
:forli_num_layers_o3 => :NL_O3)
2650
end
2751

28-
function get_iasi_l2_flex_size(giard::GIADR_IASI_SND_02)
52+
function get_flexible_dim_fields(::Type{GIADR_IASI_SND_02_V10})
53+
return Dict(
54+
:num_pressure_levels_temp => :NLT,
55+
:num_pressure_levels_ozone => :NLO,
56+
:num_pressure_levels_humidity => :NLQ,
57+
:num_surface_emissivity_wavelengths => :NEW)
58+
end
59+
60+
function get_iasi_l2_flex_size(giard::T) where {T <: GIADR_IASI_SND_02}
2961
flex_size_prod = Dict{Symbol, Int64}()
3062
giard_size_fields = get_flexible_dim_fields(typeof(giard))
3163

@@ -36,6 +68,11 @@ function get_iasi_l2_flex_size(giard::GIADR_IASI_SND_02)
3668
end
3769

3870
# computed dimensions
71+
_add_computed_dimension!(flex_size_prod, T)
72+
return flex_size_prod
73+
end
74+
75+
function _add_computed_dimension!(flex_size_prod::Dict, ::Type{GIADR_IASI_SND_02_V11})
3976
flex_size_prod[:NEVA_CO] = ceil(Int64, flex_size_prod[:NL_CO] / 2)
4077
flex_size_prod[:NEVE_CO] = flex_size_prod[:NEVA_CO] * flex_size_prod[:NL_CO]
4178
flex_size_prod[:NEVA_HNO3] = ceil(Int64, flex_size_prod[:NL_HNO3] / 2)
@@ -45,6 +82,21 @@ function get_iasi_l2_flex_size(giard::GIADR_IASI_SND_02)
4582
flex_size_prod[:NERRT] = Int64(flex_size_prod[:NPCT] * (flex_size_prod[:NPCT] + 1) / 2)
4683
flex_size_prod[:NERRW] = Int64(flex_size_prod[:NPCW] * (flex_size_prod[:NPCW] + 1) / 2)
4784
flex_size_prod[:NERRO] = Int64(flex_size_prod[:NPCO] * (flex_size_prod[:NPCO] + 1) / 2)
85+
return nothing
86+
end
4887

49-
return flex_size_prod
88+
function _add_computed_dimension!(flex_size_prod::Dict, ::Type{GIADR_IASI_SND_02_V10})
89+
return nothing
90+
end
91+
92+
function get_field_dimensions(T::Type{GIADR_IASI_SND_02_V11}, field::Symbol)
93+
return [string(get_raw_format_dim(T)[field][1])]
94+
end
95+
96+
function get_field_dimensions(T::Type{GIADR_IASI_SND_02_V10}, field::Symbol)
97+
if field == :pressure_levels_ozone
98+
return [IASI_L2_V10_03_PRESSURE_DIM, string(get_raw_format_dim(T)[field][2])]
99+
end
100+
101+
return [string(get_raw_format_dim(T)[field][1])]
50102
end

src/Instruments/IASI/IASI_records.jl

+25-6
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,28 @@ function data_record_type(header::MainProductHeader, product_type::Val{:IASI_xxx
1515
end
1616
end
1717

18-
IASI_SND_02_V11_format = joinpath(@__DIR__, "csv_formats/IASI_SND_02_V11.csv")
18+
######### level 2###########
1919

2020
abstract type IASI_SND_02 <: DataRecord end
21+
22+
IASI_SND_02_V11_format = joinpath(@__DIR__, "csv_formats/IASI_SND_02_V11.csv")
23+
IASI_SND_02_V10_format = joinpath(@__DIR__, "csv_formats/IASI_SND_02_V10.csv")
24+
2125
# create data structure, add description and scale factors
2226
eval(record_struct_expression(IASI_SND_02_V11_format, IASI_SND_02))
27+
eval(record_struct_expression(IASI_SND_02_V10_format, IASI_SND_02))
2328

2429
function data_record_type(header::MainProductHeader, product_type::Val{:IASI_SND_02})::Type
2530
if header.format_major_version == 11
2631
return IASI_SND_02_V11
32+
elseif header.format_major_version == 10
33+
return IASI_SND_02_V10
2734
else
2835
error("No format found for format major version :$(header.format_major_version)")
2936
end
3037
end
3138

32-
function get_flexible_dim_fields(::Type{<:IASI_SND_02})
39+
function get_flexible_dim_fields(::Type{IASI_SND_02_V11})
3340
return Dict(
3441
:nerr => :NERR,
3542
:co_nbr => :CO_NBR,
@@ -38,18 +45,26 @@ function get_flexible_dim_fields(::Type{<:IASI_SND_02})
3845
)
3946
end
4047

48+
function get_flexible_dim_fields(::Type{IASI_SND_02_V10})
49+
return Dict{Symbol, Symbol}(
50+
)
51+
end
52+
53+
_get_giard_type(T::Type{IASI_SND_02_V11}) = GIADR_IASI_SND_02_V11
54+
_get_giard_type(T::Type{IASI_SND_02_V10}) = GIADR_IASI_SND_02_V10
55+
4156
function _get_flexible_dims_file(file_pointer::IO, T::Type{<:IASI_SND_02})
4257
pos = position(file_pointer)
4358
seekstart(file_pointer)
44-
giard = read_first_record(file_pointer, GIADR_IASI_SND_02_V11)
59+
giard = read_first_record(file_pointer, _get_giard_type(T))
4560
flexible_dims_file = get_iasi_l2_flex_size(giard)
4661
seek(file_pointer, pos)
4762
return flexible_dims_file
4863
end
4964

50-
# IASI_SND_02 needs to have a fill value for VInteger and BitString
65+
# IASI_SND_02_V11 needs to have a fill value for VInteger and BitString
5166
function get_missing_value(
52-
record_type::Type{<:IASI_SND_02}, ::Type{VInteger{T}}, field::Symbol) where {T}
67+
record_type::Type{IASI_SND_02_V11}, ::Type{VInteger{T}}, field::Symbol) where {T}
5368

5469
# Only set missing value for variable fields to avoid masking real data
5570
if !fixed_size(record_type, field)
@@ -60,7 +75,7 @@ function get_missing_value(
6075
end
6176

6277
function get_missing_value(
63-
record_type::Type{<:IASI_SND_02}, ::Type{BitString{N}}, field::Symbol) where {N}
78+
record_type::Type{IASI_SND_02_V11}, ::Type{BitString{N}}, field::Symbol) where {N}
6479

6580
# Only set missing value for variable fields to avoid masking real data
6681
if !fixed_size(record_type, field)
@@ -70,3 +85,7 @@ function get_missing_value(
7085
return nothing
7186
end
7287
end
88+
89+
##
90+
const IASI_L2_V10_ERROR_DATA_NAME = :error_data
91+
const IASI_L2_V10_ERROR_DATA_DESCRIPTION = "Contents depend on MDR.FLG_STER field (Data is not parsed)"

0 commit comments

Comments
 (0)