Geoscience data
Gridded and geospatial data carry their meaning in metadata: units, fill values,
calendars, the coordinate reference system and the cell geometry. Most wrong answers come
from ignoring one of them, such as averaging over a latitude-longitude grid without
weights, treating _FillValue as zero, or mixing degrees with metres.
NetCDF and HDF5
xr.open_dataset(path)decodes CF metadata:scale_factorandadd_offsetunpack integers,_FillValueandmissing_valuebecome NaN, and time is decoded fromunits("days since 1850-01-01") with thecalendarattribute. Usedecode_times=Falseto see raw numbers,use_cftime=Truefornoleapor360_daycalendars and for dates outside the range of nanoseconddatetime64. Multi-file:xr.open_mfdataset(pattern, combine="by_coords", chunks={...})with dask. Inspect withncdump -hords.info(), and readunits,standard_name,cell_methodsandboundsfor every variable you use. HDF5 that is not NetCDF-4 opens withh5py.- Dimensions are axes (
time,lat,lon); coordinates are labels on them. On a curvilinear gridlat(y, x)is a two-dimensional coordinate andselby latitude no longer works directly. Longitude runs 0 to 360 in many models and -180 to 180 in others; convert withds.assign_coords(lon=((ds.lon + 180) % 360) - 180).sortby("lon"). Latitude often descends from 90 to -90, sosel(lat=slice(a, b))needs that order. - Time stamps of monthly means may mark the start, middle or end of the month; consult
time_bnds. Yearly means from monthly means must weight byds.time.dt.days_in_month.ds.resample(time="1MS").mean()for calendar months. Times are UTC unless the file says otherwise. - Writing:
ds.to_netcdf(path, encoding=enc)with a per-variableencsuch as{"var": {"dtype": "float32", "_FillValue": -9999.0, "zlib": True, "complevel": 4}}; keepunitsandstandard_nameattributes on every variable, and never let a fill value coincide with a valid data value.
Area weighting and regridding
- On a regular latitude-longitude grid cell area scales with cos(latitude):
w = np.cos(np.deg2rad(ds.lat)); ds.weighted(w).mean(("lat", "lon")). Prefer an explicitcell_areavariable or areas computed fromlat_bndswhen present. Unweighted means over-represent the poles. Totals (mass, volume, emissions) need true areas in square metres and a stated Earth radius (6371 km) or ellipsoid. - Regridding: conservative for fluxes and extensive quantities, bilinear for smooth
intensive fields, nearest for categorical data (xesmf implements all three;
xr.interpis bilinear). Name the method and the target grid.
Raster
rasterio.open(path)exposescrs,transform(affine pixel-to-world),nodata,res,boundsandtags().src.read(1, masked=True)honours nodata. GeoTIFFAREA_OR_POINTsays whether a pixel value describes the cell or its centre. Reproject withrasterio.warp.calculate_default_transformandreproject(..., resampling=Resampling.bilinear);Resampling.nearestfor classes,averageorsumwhen coarsening. GDAL equivalents:gdalinfo -stats,gdalwarp -t_srs EPSG:3035 -tr 100 100 -r bilinear -dstnodata -9999 in.tif out.tif,gdal_translate -of COG.rioxarray.open_rasterio(path)gives an xarray with.rio.crs,.rio.reproject("EPSG:...")and.rio.write_nodata(...).
Coordinate reference systems
- EPSG:4326 is WGS 84 latitude-longitude in degrees; its authority axis order is
latitude first while most software expects longitude first, so create transformers
with
pyproj.Transformer.from_crs(4326, 3857, always_xy=True). EPSG:3857 (Web Mercator) distorts area and is for tiles, not measurement. UTM zones are EPSG:326xx north and 327xx south; equal-area choices include EPSG:3035 (Europe LAEA), EPSG:5070 (CONUS Albers) and EPSG:6933 (global EASE-Grid 2.0).pyproj.CRS.from_epsg(code)exposesaxis_info,is_geographicand units. - Distances and areas on geographic coordinates are computed geodesically, not in
degrees:
pyproj.Geod(ellps="WGS84").inv(lon1, lat1, lon2, lat2)returns metres, andgeod.geometry_area_perimeter(polygon)returns square metres. Haversine is spherical and off by up to half a percent; say which formula was used. Datum shifts (WGS 84, NAD83, ETRS89) matter at the metre level; elevation needs a stated vertical datum.
Vector
geopandas.read_file(path)for shapefile, GeoPackage and GeoJSON;gdf.crs,gdf.to_crs(epsg=...),gdf.estimate_utm_crs()for a local metric CRS, then.areaand.lengthin that CRS's units (geographic CRS results are warned about and wrong).sjoin(predicate="intersects"),overlay,dissolve; validate withis_validandmake_valid. Shapefiles limit field names to 10 characters, files to 2 GB, one geometry type per layer and need.shp,.shx,.dbfand.prjtogether; prefer GeoPackage. Buffers in degrees are meaningless; buffer in a projected CRS.
Units and reporting
- Convert explicitly (K to degrees Celsius, kg m^-2 s^-1 to mm/day by 86400, Pa to hPa) or
with
pintandcf_xarray; put units in every column header. - State CRS (EPSG), resolution, resampling, weighting, calendar, fill handling, and the provenance of each file (URL, version, download date).
Sources
- CF Metadata Conventions: https://cfconventions.org/
- xarray user guide (I/O, weighted reductions, time series): https://docs.xarray.dev/en/stable/user-guide/
- netCDF4-python: https://unidata.github.io/netcdf4-python/
- rasterio reprojection: https://rasterio.readthedocs.io/en/stable/topics/reproject.html
- GDAL
gdalwarp: https://gdal.org/en/stable/programs/gdalwarp.html - pyproj
TransformerandGeod: https://pyproj4.github.io/pyproj/stable/api/ - GeoPandas projections guide: https://geopandas.org/en/stable/docs/user_guide/projections.html
- EPSG Geodetic Parameter Registry: https://epsg.org/