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 support for loading CRS in CDM formats #122

Merged
merged 8 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
95 changes: 92 additions & 3 deletions src/extra/cdm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@
cnames = filter(!isnothing, [xname, yname, zname])
isempty(cnames) && error("coordinates not found")
coords = map(nm -> _var2array(ds[nm], lazy), cnames)
N = length(cnames)

grid = if all(ndims(a) == 1 for a in coords)
RectilinearGrid(coords...)
else
if !all(ndims(a) == 1 for a in coords)
error("invalid grid arrays")
end

Expand All @@ -35,6 +34,38 @@
issetequal(vdims, cnames) || issetequal(vdims, dnames)
end

# get grid mapping (CRS)
gridmappings = map(vnames) do name
var = ds[name]
attribs = CDM.attribnames(var)
if "grid_mapping" ∈ attribs
CDM.attrib(var, "grid_mapping")
else
nothing
end
end

# convert grid mapping to CRS
crs = if all(isnothing, gridmappings)
nothing
else
if !allequal(gridmappings)
error("all variables must have the same CRS")

Check warning on line 53 in src/extra/cdm.jl

View check run for this annotation

Codecov / codecov/patch

src/extra/cdm.jl#L53

Added line #L53 was not covered by tests
end

gridmapping = first(gridmappings)
_gm2crs(ds[gridmapping])
end

# construct grid with CRS and Manifold
C = isnothing(crs) ? Cartesian{NoDatum,N,Met{Float64}} : crs
grid = if C <: LatLon
lons, lats = coords
RectilinearGrid{🌐,C}(lats, lons)
else
RectilinearGrid{𝔼{N},C}(coords...)
end

vtable = if isempty(vnames)
nothing
else
Expand Down Expand Up @@ -107,6 +138,18 @@
end
end

# reference of ellipsoid names: https://raw.githubusercontent.com/wiki/cf-convention/cf-conventions/csv/ellipsoid.csv
const ELLIP2DATUM = Dict(
"WGS 84" => WGS84Latest,
"GRS 1980" => ITRFLatest,
"Airy 1830" => OSGB36,
"Airy Modified 1849" => Ire65,
"Bessel 1841" => Hermannskogel,
"International 1924" => NZGD1949,
"Clarke 1880 (IGN)" => Carthage,
"GRS 1967 Modified" => SAD69
)

function _gribdataset(fname)
if Sys.iswindows()
error("loading GRIB files is currently not supported on Windows")
Expand Down Expand Up @@ -169,3 +212,49 @@
name
end
end

function _gm2crs(gridmapping)
attribs = CDM.attribnames(gridmapping)

# get datum from reference ellipsoid
D = if "reference_ellipsoid_name" ∈ attribs
ellip = CDM.attrib(gridmapping, "reference_ellipsoid_name")
ELLIP2DATUM[ellip]
else
WGS84Latest
end

# shift parameters
function shift()
lonₒ = "longitude_of_central_meridian" ∈ attribs ? CDM.attrib(gridmapping, "longitude_of_central_meridian") : 0.0
xₒ = "false_easting" ∈ attribs ? CDM.attrib(gridmapping, "false_easting") : 0.0
yₒ = "false_northing" ∈ attribs ? CDM.attrib(gridmapping, "false_northing") : 0.0
CoordRefSystems.Shift(lonₒ=lonₒ * u"°", xₒ=xₒ * u"m", yₒ=yₒ * u"m")
end

# parse CRS type and parameters
# reference: https://cfconventions.org/cf-conventions/cf-conventions.html#appendix-grid-mappings
gmname = CDM.attrib(gridmapping, "grid_mapping_name")
if gmname == "latitude_longitude"
LatLon{D,Deg{Float64}}
elseif gmname == "lambert_cylindrical_equal_area"
latₜₛ = if "standard_parallel" ∈ attribs
CDM.attrib(gridmapping, "standard_parallel")
elseif "scale_factor_at_projection_origin" ∈ attribs
CDM.attrib(gridmapping, "scale_factor_at_projection_origin")

Check warning on line 244 in src/extra/cdm.jl

View check run for this annotation

Codecov / codecov/patch

src/extra/cdm.jl#L241-L244

Added lines #L241 - L244 were not covered by tests
end
CoordRefSystems.EqualAreaCylindrical{latₜₛ * u"°",D,shift(),Met{Float64}}

Check warning on line 246 in src/extra/cdm.jl

View check run for this annotation

Codecov / codecov/patch

src/extra/cdm.jl#L246

Added line #L246 was not covered by tests
elseif gmname == "mercator"
Mercator{D,shift(),Met{Float64}}

Check warning on line 248 in src/extra/cdm.jl

View check run for this annotation

Codecov / codecov/patch

src/extra/cdm.jl#L248

Added line #L248 was not covered by tests
elseif gmname == "orthographic"
latₒ = CDM.attrib(gridmapping, "latitude_of_projection_origin")
Mode = CoordRefSystems.EllipticalMode
CoordRefSystems.Orthographic{Mode,latₒ * u"°",D,shift(),Met{Float64}}

Check warning on line 252 in src/extra/cdm.jl

View check run for this annotation

Codecov / codecov/patch

src/extra/cdm.jl#L250-L252

Added lines #L250 - L252 were not covered by tests
elseif gmname == "transverse_mercator"
k₀ = CDM.attrib(gridmapping, "scale_factor_at_central_meridian")
latₒ = CDM.attrib(gridmapping, "latitude_of_projection_origin")
TransverseMercator{k₀,latₒ * u"°",D,shift(),Met{Float64}}
else
nothing

Check warning on line 258 in src/extra/cdm.jl

View check run for this annotation

Codecov / codecov/patch

src/extra/cdm.jl#L258

Added line #L258 was not covered by tests
end
end
4 changes: 4 additions & 0 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
# Licensed under the MIT License. See LICENSE in the project root.
# ------------------------------------------------------------------

# helper type alias
const Met{T} = Quantity{T,u"𝐋",typeof(u"m")}
const Deg{T} = Quantity{T,NoDims,typeof(u"°")}

function asgeotable(table)
crs = GI.crs(table)
cols = Tables.columns(table)
Expand Down
Binary file added test/data/test_latlon.nc
Binary file not shown.
Binary file added test/data/test_latlon_itrf.nc
Binary file not shown.
Binary file added test/data/test_utm_north_32.nc
Binary file not shown.
19 changes: 19 additions & 0 deletions test/io/netcdf.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,25 @@
@test length(first(vtable.tempanomaly)) == 100
@test length(vtable.data) == nvertices(gtb.geometry)
@test eltype(vtable.data) <: Float64

# CRS
file = joinpath(datadir, "test_latlon.nc")
gtb = GeoIO.load(file)
@test gtb.geometry isa RectilinearGrid
@test crs(gtb.geometry) <: LatLon
@test datum(crs(gtb.geometry)) === WGS84Latest

file = joinpath(datadir, "test_latlon_itrf.nc")
gtb = GeoIO.load(file)
@test gtb.geometry isa RectilinearGrid
@test crs(gtb.geometry) <: LatLon
@test datum(crs(gtb.geometry)) === ITRFLatest

file = joinpath(datadir, "test_utm_north_32.nc")
gtb = GeoIO.load(file)
@test gtb.geometry isa RectilinearGrid
@test crs(gtb.geometry) <: TransverseMercator
@test datum(crs(gtb.geometry)) === WGS84Latest
end

@testset "save" begin
Expand Down