r3.out.netcdf(1grass)
π r3.out.netcdf π
okay bestie, welcome to r3.out.netcdf from the GRASS User's Manual β say it with me, "r-three-out-net-c-d-f" π it's a GRASS module and it's literally the plug that gets your 3D raster map out of GRASS and into a netCDF file. no cap.
β¨ NAME β¨
r3.out.netcdf β Export a 3D raster map as netCDF file. that's it, that's the whole job, and it does it gorgeously π¦
π KEYWORDS π
raster3d, export, output, netCDF, voxel
iykyk, those are the searchable tags. type them into the module search and this baby pops right up π
π SYNOPSIS π
r3.out.netcdf
r3.out.netcdf --help
r3.out.netcdf [-pm] input=name output=name [null=float] [--overwrite]
[--help] [--verbose] [--quiet] [--ui]
π Flags:
-pβ Export projection information as wkt and proj4 parameter. so like, she's bringing the receipts of where your data lives on the planet β¨-mβ Use 3D raster mask (if exists) with input map. only if there is one, bestie, she's not making it up π«--overwriteβ Allow output files to overwrite existing files. (danger-adjacent, she will absolutely clobber that thing you already had π)--helpβ Print usage summary. the "help me pls" button π«Ά--verboseβ Verbose module output. she will tell you everything she's doing π¬--quietβ Quiet module output. she goes silent-mode π§--uiβ Force launching GUI dialog. pops the whole thing open in a window, very main character π
π Parameters:
input=name[required] β Name of input 3D raster map. you gotta tell her which map, no guessing games πΈoutput=name[required] β Name for netCDF output file. and what you want the file to be called πnull=floatβ The value to be used for null values, default is the netCDF standard. so like, what number means "nothing here" β if you don't say, netCDF's own standard gets used π³οΈ
β¨ DESCRIPTION β¨
so the module r3.out.netcdf exports a 3D raster map as a netCDF file. periodt. π
the maps it works with are valid 3D raster maps that live in the current mapset search path. she's not importing from your random Downloads folder, she's looking where GRASS looks, ok?
the output parameter is the name of the netCDF file, and β heads up β it gets written into the current working directory. that's where it lands, so know where you're standing when you run her π
also? she is sensitive. to the mapset region settings (the ones you set with g.region) and to the vertical unit settings of the 3D raster map. that vertical unit? you set it with r3.support. so if the vibes are off, check those two first, bestie π
π¦ NOTES π¦
okay this is the part where she gets specific, listen up.
the resulting netCDF file will have time as the third dimension in case a temporal vertical unit is specified for the input map. otherwise, that third dimension is assumed as spatial. two totally different vibes depending on one setting, wild.
if the 3D raster map has a time stamp with absolute time, then the start time will be used as the starting point for the netCDF time series. and if that absolute time stamp is missing? the date "Jan. 01. 1900 00:00:00" gets used as the default. yes, 1900. she's vintage like that π°οΈ
in case of relative time stamp, no start time is set in the netCDF file at all. nothing. she leaves it blank π«₯
the number of depths of the 3D raster map are the number of time slices in the netCDF file. that's the mapping, memorize it fr fr.
and here's a little combo for you β you can use t.rast.to.rast3 to convert a space time raster dataset into a 3D raster map representing a space time voxel cube, and then export the raster time series as a netCDF file. slay, ate, no crumbs π½οΈ
spatial coordinates are exported as cell centered coordinates. important. don't @ me about corners.
the projection can optionally be stored in the metadata as crs attributes. π
the netCDF projection metadata storage follows the spatial_ref GDAL/netCDF suggestion here, and the netCDF CF 1.6 convention here, using WKT projection information. additionally, a PROJ string is stored in the crs attribute section. so she's thorough, she's giving professional π
and the export of projection parameters is suppressed when the XY-projection is set. no projection info for you if there's no projection to speak of, makes sense π§
the range of the 3D raster map is set in the netCDF output file. π
optionally, a the netCDF missing value and _FillValue can be set using the null option. as default, the netCDF floating point _FillValues are used to fill empty cells. so empty cells get the standard fill, unless you say otherwise β¨
last thing, and it's a real one: the netCDF library must be installed on the system and activated at configuration time to compile this module. no library, no module, no slay. π
more coming bestie, hold on π
β¨ EXAMPLES β¨
ok bestie this is the fun part, we're gonna actually do the thing π
First up, set your region β this is literally the same region the whole example runs on, so don't skip it:
g.region s=-90 n=90 w=-180 e=180 b=0 t=5 res=10 res3=10 tbres=1 -p3
#####################################################################
# We create a simple volume map with floating point values
#####################################################################
r3.mapcalc --o expr="volume_float = float(col() + row() + depth())"
r3.info volume_float
so we made a lil 3D volume map with floating point values (FCELL, no cap) where every cell is just col() + row() + depth() β iconic. And r3.info spills all the tea about it:
+----------------------------------------------------------------------------+
| Layer: volume_float Date: Thu Jun 14 08:40:56 2012 |
| Mapset: PERMANENT Login of Creator: soeren |
| Project: TestLL |
| DataBase: /1/soeren/grassdata |
| Title: volume_float |
| Units: none |
| Vertical unit: units |
| Timestamp: none |
|----------------------------------------------------------------------------|
| |
| Type of Map: 3d cell Number of Categories: 0 |
| Data Type: FCELL |
| Rows: 18 |
| Columns: 36 |
| Depths: 5 |
| Total Cells: 3240 |
| Total size: 10114 Bytes |
| Number of tiles: 1 |
| Mean tile size: 10114 Bytes |
| Tile size in memory: 12960 Bytes |
| Number of tiles in x, y and z: 1, 1, 1 |
| Dimension of a tile in x, y, z: 36, 18, 5 |
| |
| Projection: Latitude-Longitude (zone 0) |
| N: 90N S: 90S Res: 10 |
| E: 180E W: 180W Res: 10 |
| T: 5 B: 0 Res: 1 |
| Range of data: min = 3 max = 59 |
| |
| Data Source: |
| |
| |
| |
| Data Description: |
| generated by r3.mapcalc |
| |
| Comments: |
| r3.mapcalc expression="volume_float = float(col() + row() + depth())" |
| |
+----------------------------------------------------------------------------+
π¦ peeking at the header with ncdump
Next, we export that volume to netCDF and let ncdump show us the goods β headers, coordinates, the whole vibe check:
#####################################################################
# We use the netCDF ncdump tool to have a look at the header
# and coordinates of the exported netCDF file
#####################################################################
r3.out.netcdf --o input=volume_float output=test_float.nc
ncdump -c test_float.nc
and it gives us this:
netcdf test_float {
dimensions:
longitude = 36 ;
latitude = 18 ;
z = 5 ;
variables:
float longitude(longitude) ;
longitude:units = "degrees_east" ;
longitude:long_name = "Longitude values" ;
longitude:standard_name = "longitude" ;
longitude:axis = "X" ;
float latitude(latitude) ;
latitude:units = "degrees_north" ;
latitude:long_name = "Latitude values" ;
latitude:standard_name = "latitude" ;
latitude:axis = "Y" ;
float z(z) ;
z:units = "meter" ;
z:long_name = "z coordinate of projection" ;
z:standard_name = "projection_z_coordinate" ;
z:positive = "up" ;
z:axis = "Z" ;
float volume_float(z, latitude, longitude) ;
volume_float:valid_min = 3.f ;
volume_float:valid_max = 59.f ;
// global attributes:
:Conventions = "CF-1.5" ;
:history = "GRASS GIS netCDF export of r3.out.netcdf" ;
data:
longitude = -175, -165, -155, -145, -135, -125, -115, -105, -95, -85, -75,
-65, -55, -45, -35, -25, -15, -5, 5, 15, 25, 35, 45, 55, 65, 75, 85, 95,
105, 115, 125, 135, 145, 155, 165, 175 ;
latitude = 85, 75, 65, 55, 45, 35, 25, 15, 5, -5, -15, -25, -35, -45, -55,
-65, -75, -85 ;
z = 0.5, 1.5, 2.5, 3.5, 4.5 ;
}
look at those units, long_name, standard_name and axis attributes β that's the map metadata getting carried over, fr fr β¨
π exporting projection settings and a null value
now we add the projection-specific stuff and tell it what a null value looks like, using -p and null=-1:
#####################################################################
# Exporting the projection specific settings and a null value
#####################################################################
r3.out.netcdf --o -p null=-1 input=volume_float output=test_float.nc
ncdump -c test_float.nc
and boom, we gained a whole crs variable plus missing_value, _FillValue and grid_mapping:
netcdf test_float {
dimensions:
longitude = 36 ;
latitude = 18 ;
z = 5 ;
variables:
char crs ;
crs:crs_wkt = "GEOGCS[\"wgs84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS_1984\",6378137,298.257223563]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]]" ;
crs:spatial_ref = "GEOGCS[\"wgs84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS_1984\",6378137,298.257223563]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]]" ;
crs:crs_proj4 = " +proj=longlat +no_defs +a=6378137 +rf=298.257223563 +towgs84=0.000,0.000,0.000" ;
float longitude(longitude) ;
longitude:units = "degrees_east" ;
longitude:long_name = "Longitude values" ;
longitude:standard_name = "longitude" ;
longitude:axis = "X" ;
float latitude(latitude) ;
latitude:units = "degrees_north" ;
latitude:long_name = "Latitude values" ;
latitude:standard_name = "latitude" ;
latitude:axis = "Y" ;
float z(z) ;
z:units = "meter" ;
z:long_name = "z coordinate of projection" ;
z:standard_name = "projection_z_coordinate" ;
z:positive = "up" ;
z:axis = "Z" ;
float volume_float(z, latitude, longitude) ;
volume_float:valid_min = 3.f ;
volume_float:valid_max = 59.f ;
volume_float:missing_value = -1.f ;
volume_float:_FillValue = -1.f ;
volume_float:grid_mapping = "crs" ;
// global attributes:
:Conventions = "CF-1.5" ;
:history = "GRASS GIS netCDF export of r3.out.netcdf" ;
data:
longitude = -175, -165, -155, -145, -135, -125, -115, -105, -95, -85, -75,
-65, -55, -45, -35, -25, -15, -5, 5, 15, 25, 35, 45, 55, 65, 75, 85, 95,
105, 115, 125, 135, 145, 155, 165, 175 ;
latitude = 85, 75, 65, 55, 45, 35, 25, 15, 5, -5, -15, -25, -35, -45, -55,
-65, -75, -85 ;
z = 0.5, 1.5, 2.5, 3.5, 4.5 ;
}
π time as the vertical unit + an absolute time stamp
okay now we're getting fancy π
we stamp the map with actual dates, set the vertical unit to days, and export again:
#####################################################################
# Assigning time as vertical unit and setting an absolute time stamp
#####################################################################
r3.timestamp map=volume_float date=β1 Jan 2001/5 Jan 2001β
r3.support map=volume_float vunit="days"
r3.out.netcdf --o -p null=-1 input=volume_float output=test_float.nc
ncdump -c test_float.nc
and now the z dimension straight up becomes time, with units, long_name, calendar, positive and axis all set:
netcdf test_float {
dimensions:
longitude = 36 ;
latitude = 18 ;
time = 5 ;
variables:
char crs ;
crs:crs_wkt = "GEOGCS[\"wgs84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS_1984\",6378137,298.257223563]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]]" ;
crs:spatial_ref = "GEOGCS[\"wgs84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS_1984\",6378137,298.257223563]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]]" ;
crs:crs_proj4 = " +proj=longlat +no_defs +a=6378137 +rf=298.257223563 +towgs84=0.000,0.000,0.000" ;
float longitude(longitude) ;
longitude:units = "degrees_east" ;
longitude:long_name = "Longitude values" ;
longitude:standard_name = "longitude" ;
longitude:axis = "X" ;
float latitude(latitude) ;
latitude:units = "degrees_north" ;
latitude:long_name = "Latitude values" ;
latitude:standard_name = "latitude" ;
latitude:axis = "Y" ;
int time(time) ;
time:units = "days since 2001-01-01 00:00:00" ;
time:long_name = "Time in days" ;
time:calendar = "gregorian" ;
time:positive = "up" ;
time:axis = "T" ;
float volume_float(time, latitude, longitude) ;
volume_float:valid_min = 3.f ;
volume_float:valid_max = 59.f ;
volume_float:missing_value = -1.f ;
volume_float:_FillValue = -1.f ;
volume_float:grid_mapping = "crs" ;
// global attributes:
:Conventions = "CF-1.5" ;
:history = "GRASS GIS netCDF export of r3.out.netcdf" ;
data:
longitude = -175, -165, -155, -145, -135, -125, -115, -105, -95, -85, -75,
-65, -55, -45, -35, -25, -15, -5, 5, 15, 25, 35, 45, 55, 65, 75, 85, 95,
105, 115, 125, 135, 145, 155, 165, 175 ;
latitude = 85, 75, 65, 55, 45, 35, 25, 15, 5, -5, -15, -25, -35, -45, -55,
-65, -75, -85 ;
time = 0, 1, 2, 3, 4 ;
}
periodt. five depths in, five time steps out π«Ά
π SEE ALSO
t.rast.to.rast3r3.in.asciig.region
π AUTHORS
SΓΆren Gebbert
β¨ SOURCE CODE β¨
ok so if you wanna go look at the actual code (and honestly? slay, do it) it's right here:
- Available at:
r3.out.netcdfsource code (history)
Latest change: Monday Dec 08 21:47:55 2025 in commit:
d3c6479bef77a58e3b5d85259303dbfb9c1babe8
and then there's the lil index fam, all of them linking you deeper into the docs β collect them all like they're pokΓ©mon bestie π
- Main index
- 3D raster index
- Topics index
- Keywords index
- Graphical index
- Full index
Β© 2003-2026 GRASS Development Team, GRASS 8.5.0 Reference Manual
okay that's the whole page, you made it, I'm so proud of you π«Ά This absolute icon of a manpage is straight outta the GRASS 8.5.0 reference manual, and the last glow-up on it was that Monday Dec 08 21:47:55 2025 commit β so it's fresh, it's current, it's giving maintained. Now go write some netCDF files, bestie. no cap. π β¨
π¦ where this page comes from
- alpine 3.24: grass-gis-doc 8.5.0-r1 Β· GPL-2.0-or-later
- ubuntu stonking: grass-doc 8.5.0-1ubuntu1 Β· GPL-2.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms