All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ucar.nc2.dataset.conv.CF1Convention Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 1998-2020 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.nc2.dataset.conv;

import com.google.common.collect.ImmutableMap;
import ucar.nc2.*;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants._Coordinate;
import ucar.nc2.constants.AxisType;
import ucar.nc2.constants.CF;
import ucar.nc2.time.CalendarDateUnit;
import ucar.nc2.units.SimpleUnit;
import ucar.nc2.util.CancelTask;
import ucar.nc2.dataset.*;
import ucar.ma2.Array;
import ucar.ma2.IndexIterator;
import ucar.ma2.DataType;
import java.util.*;
import java.io.IOException;

/**
 * CF-1 Convention. see http://www.cgd.ucar.edu/cms/eaton/cf-metadata/index.html
 * 

* * "The CF conventions for climate and forecast metadata are designed to promote the processing and sharing of files * created with the netCDF * API. The conventions define metadata that provide a definitive description of what the data in each variable * represents, and of the * spatial and temporal properties of the data. This enables users of data from different sources to decide which * quantities are comparable, * and facilitates building applications with powerful extraction, regridding, and display capabilities." * * * @author caron */ public class CF1Convention extends CSMConvention { private static final String convName = "CF-1."; // start with /** * Get which CF version this is, ie CF-1.x * * @param hasConvName extract from convention name or list of names * @return version, or -1 if not CF */ public static int getVersion(String hasConvName) { int result = extractVersion(hasConvName); if (result >= 0) { return result; } List names = breakupConventionNames(hasConvName); for (String name : names) { result = extractVersion(name); if (result >= 0) { return result; } } return -1; } private static int extractVersion(String hasConvName) { if (!hasConvName.startsWith(convName)) { return -1; } String versionS = hasConvName.substring(convName.length()); try { return Integer.parseInt(versionS); } catch (Exception e) { return -1; } } /** * Guess the value of ZisPositive based on z axis name and units * * @param zaxisName z coordinate axis name * @param vertCoordUnits z coordinate axis name * @return CF.POSITIVE_UP or CF.POSITIVE_DOWN */ public static String getZisPositive(String zaxisName, String vertCoordUnits) { if (vertCoordUnits == null) { return CF.POSITIVE_UP; } if (vertCoordUnits.isEmpty()) { return CF.POSITIVE_UP; } if (SimpleUnit.isCompatible("millibar", vertCoordUnits)) { return CF.POSITIVE_DOWN; } if (SimpleUnit.isCompatible("m", vertCoordUnits)) { return CF.POSITIVE_UP; } // dunno - make it up return CF.POSITIVE_UP; } private static final String[] vertical_coords = {"atmosphere_ln_pressure_coordinate", "atmosphere_sigma_coordinate", "atmosphere_hybrid_sigma_pressure_coordinate", "atmosphere_hybrid_height_coordinate", "atmosphere_sleve_coordinate", "ocean_sigma_coordinate", "ocean_s_coordinate", "ocean_sigma_z_coordinate", "ocean_double_sigma_coordinate", "ocean_s_coordinate_g1", // -sachin 03/25/09 "ocean_s_coordinate_g2"}; public CF1Convention() { this.conventionName = "CF-1.X"; } public void augmentDataset(NetcdfDataset ds, CancelTask cancelTask) throws IOException { boolean got_grid_mapping = false; // look for transforms List vars = ds.getVariables(); for (Variable v : vars) { // look for special standard_names String sname = ds.findAttValueIgnoreCase(v, CF.STANDARD_NAME, null); if (sname != null) { sname = sname.trim(); if (sname.equalsIgnoreCase(CF.atmosphere_ln_pressure_coordinate)) { // LOOK why isnt this with other Transforms? makeAtmLnCoordinate(ds, v); continue; } if (sname.equalsIgnoreCase(CF.TIME_REFERENCE)) { v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.RunTime.toString())); continue; } if (sname.equalsIgnoreCase(CF.TIME_OFFSET)) { v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.TimeOffset.toString())); continue; } if (sname.equalsIgnoreCase(CF.TIME)) { v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Time.toString())); } if (sname.equalsIgnoreCase("ensemble") || sname.equalsIgnoreCase("realization")) { v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Ensemble.toString())); continue; } for (String vertical_coord : vertical_coords) { if (sname.equalsIgnoreCase(vertical_coord)) { v.addAttribute(new Attribute(_Coordinate.TransformType, TransformType.Vertical.toString())); if (v.findAttribute(_Coordinate.Axes) == null) { v.addAttribute(new Attribute(_Coordinate.Axes, v.getFullName())); // LOOK: may also be time dependent } } } // look for time variables and check to see if they have a calendar attribute. if not, add the default checkTimeVarForCalendar(v); } // look for horiz transforms. only ones that are referenced by another variable. String grid_mapping = ds.findAttValueIgnoreCase(v, CF.GRID_MAPPING, null); if (grid_mapping != null) { Variable gridMap = ds.findVariable(grid_mapping); if (gridMap == null) { Group g = v.getParentGroupOrRoot(); // might be group relative - CF does not specify gridMap = g.findVariableLocal(grid_mapping); } if (gridMap != null) { gridMap.addAttribute(new Attribute(_Coordinate.TransformType, TransformType.Projection.toString())); String grid_mapping_name = ds.findAttValueIgnoreCase(gridMap, CF.GRID_MAPPING_NAME, null); if (CF.LATITUDE_LONGITUDE.equals(grid_mapping_name)) { // "grid_mapping_name == latitude_longitude" is special in CF: it's applied to variables that describe // properties of lat/lon CRSes. gridMap.addAttribute(new Attribute(_Coordinate.AxisTypes, AxisType.Lat + " " + AxisType.Lon)); } else { gridMap.addAttribute(new Attribute(_Coordinate.AxisTypes, AxisType.GeoX + " " + AxisType.GeoY)); } // check for CF-ish GOES-16/17 grid mappings Attribute productionLocation = ds.findGlobalAttributeIgnoreCase("production_location"); Attribute icdVersion = ds.findGlobalAttributeIgnoreCase("ICD_version"); if (productionLocation != null && icdVersion != null) { // the fact that those two global attributes are not null means we should check to see // if the grid mapping variable has attributes that need corrected. correctGoes16(productionLocation, icdVersion, gridMap); } got_grid_mapping = true; } } // simple geometry if (ds.findGlobalAttribute(CF.CONVENTIONS) != null) { if (getVersion(ds.findGlobalAttribute(CF.CONVENTIONS).getStringValue()) >= 8) // only acknowledge simple // geometry standard extension if // CF-1.8 or higher { if (v.findAttribute(CF.GEOMETRY) != null) { Attribute container = v.findAttribute(CF.GEOMETRY); Variable coordsvar = ds.findVariable(container.getStringValue()); v.addAttribute(new Attribute(CF.GEOMETRY_TYPE, ds.findAttValueIgnoreCase(coordsvar, CF.GEOMETRY_TYPE, ""))); // Only add attribute if present, sometimes optional if (!ds.findAttValueIgnoreCase(coordsvar, CF.NODES, "").equals("")) { v.addAttribute(new Attribute(CF.NODES, ds.findAttValueIgnoreCase(coordsvar, CF.NODES, ""))); } // Only add attribute if present, sometimes optional if (!ds.findAttValueIgnoreCase(coordsvar, CF.NODE_COUNT, "").equals("")) { v.addAttribute(new Attribute(CF.NODE_COUNT, ds.findAttValueIgnoreCase(coordsvar, CF.NODE_COUNT, ""))); } v.addAttribute( new Attribute(CF.NODE_COORDINATES, ds.findAttValueIgnoreCase(coordsvar, CF.NODE_COORDINATES, ""))); v.addAttribute( new Attribute(CF.PART_NODE_COUNT, ds.findAttValueIgnoreCase(coordsvar, CF.PART_NODE_COUNT, ""))); if (CF.POLYGON.equalsIgnoreCase(ds.findAttValueIgnoreCase(coordsvar, CF.GEOMETRY_TYPE, ""))) { // Again, interior ring is not always required, but add it if it is present if (!ds.findAttValueIgnoreCase(coordsvar, CF.INTERIOR_RING, "").equals("")) { v.addAttribute( new Attribute(CF.INTERIOR_RING, ds.findAttValueIgnoreCase(coordsvar, CF.INTERIOR_RING, ""))); } } if (v.findAttribute(CF.NODE_COORDINATES) != null) { String[] coords = ds.findAttValueIgnoreCase(coordsvar, CF.NODE_COORDINATES, "").split(" "); String cds = ""; for (String coord : coords) { Variable temp = ds.findVariable(coord); if (temp != null) { Attribute axis = temp.findAttribute(CF.AXIS); if (axis != null) { if ("x".equalsIgnoreCase(axis.getStringValue())) { temp.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.SimpleGeometryX.toString())); } if ("y".equalsIgnoreCase(axis.getStringValue())) { temp.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.SimpleGeometryY.toString())); } if ("z".equalsIgnoreCase(axis.getStringValue())) { temp.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.SimpleGeometryZ.toString())); } cds += coord + " "; } } } List dims = v.getDimensions(); // Append any geometry dimensions as axis String pre = ""; for (Dimension di : dims) { if (!di.getShortName().equals("time")) { if (ds.findVariable(di.getFullNameEscaped()) != null) { ds.findVariable(di.getFullNameEscaped()) .addAttribute(new Attribute(_Coordinate.AxisType, AxisType.SimpleGeometryID.toString())); } // handle else case as malformed CF NetCDF } pre = di.getShortName() + " " + pre; } v.addAttribute(new Attribute(_Coordinate.Axes, pre + cds.trim())); } } } } } if (!got_grid_mapping) { // see if there are any grid mappings anyway for (Variable v : ds.getVariables()) { String grid_mapping_name = ds.findAttValueIgnoreCase(v, CF.GRID_MAPPING_NAME, null); if (grid_mapping_name != null) { v.addAttribute(new Attribute(_Coordinate.TransformType, TransformType.Projection.toString())); if (grid_mapping_name.equals(CF.LATITUDE_LONGITUDE)) { v.addAttribute(new Attribute(_Coordinate.AxisTypes, AxisType.Lat + " " + AxisType.Lon)); } else { v.addAttribute(new Attribute(_Coordinate.AxisTypes, AxisType.GeoX + " " + AxisType.GeoY)); } } } } // make corrections for specific datasets String src = ds.findAttValueIgnoreCase(null, "Source", ""); if (src.equals("NOAA/National Climatic Data Center")) { String title = ds.findAttValueIgnoreCase(null, "title", ""); avhrr_oiv2 = title.indexOf("OI-V2") > 0; } ds.finish(); } private boolean avhrr_oiv2; // this is here because it doesnt fit into the 3D array thing. private void makeAtmLnCoordinate(NetcdfDataset ds, Variable v) { // get the formula attribute String formula = ds.findAttValueIgnoreCase(v, CF.formula_terms, null); if (null == formula) { String msg = " Need attribute 'formula_terms' on Variable " + v.getFullName() + "\n"; parseInfo.format(msg); userAdvice.format(msg); return; } // parse the formula string Variable p0Var = null, levelVar = null; StringTokenizer stoke = new StringTokenizer(formula, " :"); while (stoke.hasMoreTokens()) { String toke = stoke.nextToken(); if (toke.equalsIgnoreCase("p0")) { String name = stoke.nextToken(); p0Var = ds.findVariable(name); } else if (toke.equalsIgnoreCase("lev")) { String name = stoke.nextToken(); levelVar = ds.findVariable(name); } } if (null == p0Var) { String msg = " Need p0:varName on Variable " + v.getFullName() + " formula_terms\n"; parseInfo.format(msg); userAdvice.format(msg); return; } if (null == levelVar) { String msg = " Need lev:varName on Variable " + v.getFullName() + " formula_terms\n"; parseInfo.format(msg); userAdvice.format(msg); return; } String units = ds.findAttValueIgnoreCase(p0Var, CDM.UNITS, "hPa"); // create the data and the variable try { // p(k) = p0 * exp(-lev(k)) double p0 = p0Var.readScalarDouble(); Array levelData = levelVar.read(); Array pressureData = Array.factory(DataType.DOUBLE, levelData.getShape()); IndexIterator ii = levelData.getIndexIterator(); IndexIterator iip = pressureData.getIndexIterator(); while (ii.hasNext()) { double val = p0 * Math.exp(-1.0 * ii.getDoubleNext()); iip.setDoubleNext(val); } CoordinateAxis1D p = new CoordinateAxis1D(ds, null, v.getShortName() + "_pressure", DataType.DOUBLE, levelVar.getDimensionsString(), units, "Vertical Pressure coordinate synthesized from atmosphere_ln_pressure_coordinate formula"); p.setCachedData(pressureData, false); p.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Pressure.toString())); p.addAttribute(new Attribute(_Coordinate.AliasForDimension, p.getDimensionsString())); ds.addVariable(null, p); parseInfo.format(" added Vertical Pressure coordinate %s from CF-1 %s%n", p.getFullName(), CF.atmosphere_ln_pressure_coordinate); } catch (IOException e) { String msg = " Unable to read variables from " + v.getFullName() + " formula_terms\n"; parseInfo.format(msg); userAdvice.format(msg); } } /* * vertical coordinate will be identifiable by: * 1. units of pressure; or * 2. the presence of the positive attribute with a value of up or down (case insensitive). * 3. Optionally, the vertical type may be indicated additionally by providing the standard_name attribute with an * appropriate value, and/or the axis attribute with the value Z. */ // we assume that coordinate axes get identified by // 1) being coordinate variables or // 2) being listed in coordinates attribute. /** * Augment COARDS axis type identification with Standard names (including dimensionless vertical coordinates) and * CF.AXIS attributes */ protected AxisType getAxisType(NetcdfDataset ncDataset, VariableEnhanced v) { // standard names for unitless vertical coords String sname = ncDataset.findAttValueIgnoreCase((Variable) v, CF.STANDARD_NAME, null); if (sname != null) { sname = sname.trim(); for (String vertical_coord : vertical_coords) { if (sname.equalsIgnoreCase(vertical_coord)) { return AxisType.GeoZ; } } } // COARDS - check units AxisType at = super.getAxisType(ncDataset, v); if (at != null) { return at; } // standard names for X, Y : bug in CDO putting wrong standard name, so check units first (!) if (sname != null) { if (sname.equalsIgnoreCase(CF.ENSEMBLE)) { return AxisType.Ensemble; } if (sname.equalsIgnoreCase(CF.LATITUDE)) { return AxisType.Lat; } if (sname.equalsIgnoreCase(CF.LONGITUDE)) { return AxisType.Lon; } if (sname.equalsIgnoreCase(CF.PROJECTION_X_COORDINATE) || sname.equalsIgnoreCase(CF.PROJECTION_X_ANG_COORDINATE) || sname.equalsIgnoreCase(CF.GRID_LONGITUDE) || sname.equalsIgnoreCase(CF.ROTATED_LONGITUDE)) { return AxisType.GeoX; } if (sname.equalsIgnoreCase(CF.PROJECTION_Y_COORDINATE) || sname.equalsIgnoreCase(CF.PROJECTION_Y_ANG_COORDINATE) || sname.equalsIgnoreCase(CF.GRID_LATITUDE) || sname.equalsIgnoreCase(CF.ROTATED_LATITUDE)) { return AxisType.GeoY; } if (sname.equalsIgnoreCase(CF.TIME_REFERENCE)) { return AxisType.RunTime; } if (sname.equalsIgnoreCase(CF.TIME_OFFSET)) { return AxisType.TimeOffset; } } // check axis attribute - only for X, Y, Z String axis = ncDataset.findAttValueIgnoreCase((Variable) v, CF.AXIS, null); if (axis != null) { axis = axis.trim(); String unit = v.getUnitsString(); if (axis.equalsIgnoreCase("X")) { if (SimpleUnit.isCompatible("m", unit)) { return AxisType.GeoX; } } else if (axis.equalsIgnoreCase("Y")) { if (SimpleUnit.isCompatible("m", unit)) { return AxisType.GeoY; } } else if (axis.equalsIgnoreCase("Z")) { if (unit == null) { return AxisType.GeoZ; } if (SimpleUnit.isCompatible("m", unit)) { return AxisType.Height; } else if (SimpleUnit.isCompatible("mbar", unit)) { return AxisType.Pressure; } else { return AxisType.GeoZ; } } } if (avhrr_oiv2) { if (v.getShortName().equals("zlev")) { return AxisType.Height; } } try { String units = v.getUnitsString(); CalendarDateUnit cd = CalendarDateUnit.of(null, units); // parsed successfully, what could go wrong? return AxisType.Time; } catch (Throwable t) { // ignore } // dunno return null; } private void correctGoes16(Attribute productionLocation, Attribute icdVersion, Variable gridMappingVar) { // Files with these global attributes might need corrected // :ICD_version = "GROUND SEGMENT (GS) TO ADVANCED WEATHER INTERACTIVE PROCESSING SYSTEM (AWIPS) INTERFACE CONTROL // DOCUMENT (ICD) Revision B" ; // :production_location = "WCDAS" ; String prodLoc = productionLocation.getStringValue(); String icdVer = icdVersion.getStringValue(); if (prodLoc != null && icdVer != null) { prodLoc = prodLoc.toLowerCase().trim(); icdVer = icdVer.toLowerCase().trim(); boolean mightNeedCorrected = prodLoc.contains("wcdas"); mightNeedCorrected = mightNeedCorrected && icdVer.contains("ground segment"); mightNeedCorrected = mightNeedCorrected && icdVer.contains("awips"); if (mightNeedCorrected) { Map possibleCorrections = ImmutableMap.of("semi_minor", CF.SEMI_MINOR_AXIS, "semi_major", CF.SEMI_MAJOR_AXIS); possibleCorrections.forEach((incorrect, correct) -> { Attribute attr = gridMappingVar.findAttributeIgnoreCase(incorrect); if (attr != null) { Array vals = attr.getValues(); if (vals != null) { gridMappingVar.addAttribute(new Attribute(correct, vals)); gridMappingVar.remove(attr); log.debug("Renamed {} attribute {} to {}", gridMappingVar, incorrect, correct); } } }); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy