/* * Copyright 1998, University Corporation for Atmospheric Research * All Rights Reserved. * See file LICENSE for copying and redistribution conditions. * * $Id: CfView.java,v 1.4 2002-10-21 20:07:45 donm Exp $ */ package visad.data.netcdf.in; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.StringTokenizer; import java.util.TreeSet; import java.util.WeakHashMap; import ucar.netcdf.Dimension; import ucar.netcdf.Netcdf; import ucar.netcdf.Variable; import ucar.netcdf.VariableIterator; import visad.CommonUnit; import visad.data.netcdf.QuantityDB; import visad.data.netcdf.QuantityDBImpl; import visad.CoordinateSystem; import visad.ErrorEstimate; import visad.GriddedSet; import visad.MathType; import visad.OffsetUnit; import visad.ProductSet; import visad.RealTupleType; import visad.RealType; import visad.SampledSet; import visad.SI; import visad.TextType; import visad.TypeException; import visad.Unit; import visad.VisADException; /** * <p> * A view of a netCDF dataset according to the Climate and Forecast (CF) * conventions. * </p> * * <p>If this class can't be initialized, then an error message is printed to * {@link System#err} and the JVM is terminated.</p> * * @author Steven R. Emmerson * @version $Revision: 1.4 $ $Date: 2002-10-21 20:07:45 $ * @see http://www.cgd.ucar.edu/cms/eaton/netcdf/CF-current.htm */ final class CfView extends View { /* * The following 6 fields are not "final" to accomodate a bug in JDK 1.2. */ private SortedSet auxCoordVars; private SortedSet boundaryVars; private Map varToRealType; private Map dimsToDomain; private Map varToAuxCoordVars; private Map varToUnitString; /* * The following 5 fields are not "final" to accomodate a bug in JDK 1.2. */ private static String[] CF_CONVENTIONS_STRINGS; private static QuantityDBImpl cfQuantityDB; private static Variable[] nilVarArray; private static Comparator varComparator; static { CF_CONVENTIONS_STRINGS = new String[] {"CF-1.0", "COARDS/CF-1.0", "COARDS"}; nilVarArray = new Variable[0]; cfQuantityDB = new QuantityDBImpl((QuantityDB)null); varComparator = new Comparator() { public int compare(Object o1, Object o2) { return ((Variable)o1).getName().compareTo( ((Variable)o2).getName()); } }; try { cfQuantityDB.add( new String[] { "pressure","Pa", "stress","Pa", "mass","kg", "area","m2", "volume","m3", "temperature","K", "thickness","m", "height","m", "altitude","m", "depth","m", "mass_fraction","1", "mass_mixing_ratio","1", "volume_fraction","1", "area_fraction","1", "heat_flux_density","W m-2", "heat_flux","W", "power","W", "mass_flux_density","kg m-2 s-1", "mass_flux","kg s-1", "volume_flux_density","m s-1", "volume_flux","m3 s-1", "energy","J", "energy_content","J m-2", "energy_density","J m-3", "content","kg m-2", "amount","kg m-2", "speed","m s-1", "velocity","m s-1", "mass","kg", "time","s", "period","s", "density","kg m-3", "longitude","degrees_E", "latitude","degrees_N", "binary_mask","1", "data_mask","1", "frequency","s-1", "frequency_of_occurrence","s-1", "probability","1", "sigma","1", "hybrid_sigma_pressure","1", "sigma_term_in_hybrid_sigma_pressure","1", "pressure_fraction_term_in_hybrid_sigma_pressure","1", "pressure_term_in_hybrid_sigma_pressure","Pa", "hybrid_height","m", "height_term_in_hybrid_height","1", "altitude_term_in_hybrid_height","1", "model_level_number","1", "forecast_reference_time","s", "forecast_period","s", "specific_eddy_kinetic_energy","m2 s-2", "sea_floor_depth","m", "partial_pressure","Pa", "surface_air_pressure","Pa", "air_pressure","Pa", "air_pressure_anomaly","Pa", "rate_of_change_of_air_pressure","Pa s-1", "air_density","kg m-3", "sea_water_density","kg m-3", "sea_water_potential_density","kg m-3", "wind_speed","m s-1", "eastward_wind","m s-1", "northward_wind","m s-1", "wind_direction","degree", "grid_eastward_wind","m s-1", "grid_northward_wind","m s-1", "air_potential_temperature","K", "soil_water_content","kg m-2", "specific_humidity","1", "mass_fraction_of_water_in_air","1", "cloud_area_fraction","1", "convective_cloud_area_fraction","1", "low_cloud_area_fraction","1", "medium_cloud_area_fraction","1", "high_cloud_area_fraction","1", "altitude_at_cloud_base","m", "air_pressure_at_cloud_base","Pa", "altitude_at_cloud_top","m", "air_pressure_at_cloud_top","Pa", "cloud_condensed_water_content","kg m-2", "atmosphere_water_content","kg m-2", "soil_temperature","K", "canopy_water_amount","kg m-2", "LWE_thickness_of_canopy_water_amount","m", "surface_snow_amount","kg m-2", "surface_snow_thickness","m", "LWE_thickness_of_surface_snow_amount","m", "surface_snow_area_fraction","1", "surface_temperature","K", "atmosphere_boundary_layer_thickness","m", "surface_roughness_length","m", "eastward_sea_water_velocity","m s-1", "northward_sea_water_velocity","m s-1", "sea_water_speed","m s-1", "direction_of_sea_water_velocity","degree", "land_binary_mask","1", "sea_ice_area_fraction","1", "sea_ice_thickness","m", "sea_ice_amount","kg m-2", "sea_ice_mass","kg", "sea_ice_area","m2", "sea_ice_extent","m2", "sea_ice_volume","m3", "sea_ice_freeboard","m", "sea_ice_draft","m", "surface_altitude","m", "surface_temperature_anomaly","K", "LWE_thickness_of_soil_water_content","m", "soil_water_content_at_field_capacity","kg m-2", "ratio_of_soil_water_content_to_soil_water_content_at_field_capacity","1", "vegetation_area_fraction","1", "root_depth","m", "surface_albedo","1", "surface_albedo_assuming_no_snow","1", "surface_albedo_assuming_deep_snow","1", "mass_fraction_of_O3_in_air","1", "molar_fraction_of_O3_in_air","1", "upward_wind","m s-1", "upward_wind_expressed_as_rate_of_change_of_sigma","s-1", "atmosphere_SO4_content","kg m-2", "land_area_fraction","1", "sea_area_fraction","1", "land_ice_area_fraction","1", "leaf_area_index","1", "canopy_height","m", "mass_fraction_of_unfrozen_water_in_soil_water","1", "mass_fraction_of_frozen_water_in_soil_water","1", "soil_frozen_water_content","kg m-2", "soil_albedo","1", "snow_soot_content","kg m-2", "atmosphere_energy_content","J m-2", "soil_carbon_content","kg m-2", "snow_grain_size","m", "snow_temperature","K", "air_temperature","K", "air_temperature_anomaly","K", "TOA_downward_radiative_heat_flux_density","W m-2", "surface_downward_shortwave_heat_flux_density","W m-2", "downward_shortwave_heat_flux_density","W m-2", "downward_longwave_heat_flux_density","W m-2", "TOA_downward_shortwave_heat_flux_density","W m-2", "TOA_incoming_shortwave_heat_flux_density","W m-2", "TOA_outgoing_shortwave_heat_flux_density","W m-2", "TOA_outgoing_shortwave_heat_flux_density_assuming_clear_sky","W m-2", "surface_incident_shortwave_heat_flux_density_assuming_clear_sky","W m-2", "surface_reflected_shortwave_heat_flux_density_assuming_clear_sky","W m-2", "surface_reflected_shortwave_heat_flux_density","W m-2", "large_scale_cloud_area_fraction","1", "rate_of_change_of_air_temperature_due_to_shortwave_heating","K s-1", "rate_of_change_of_air_temperature_due_to_shortwave_heating_assuming_clear_sky","K s-1", "surface_incident_shortwave_heat_flux_density","W m-2", "tropopause_downward_shortwave_heat_flux_density","W m-2", "tropopause_upward_shortwave_heat_flux_density_from_below","W m-2", "surface_downward_longwave_heat_flux_density","W m-2", "surface_emitted_longwave_heat_flux_density","W m-2", "surface_emitted_longwave_heat_flux_density_assuming_clear_sky","W m-2", "TOA_upward_longwave_heat_flux_density","W m-2", "TOA_downward_longwave_heat_flux_density","W m-2", "TOA_upward_longwave_heat_flux_density_assuming_clear_sky","W m-2", "surface_incident_longwave_heat_flux_density","W m-2", "surface_incident_longwave_heat_flux_density_assuming_clear_sky","W m-2", "rate_of_change_of_air_temperature_due_to_longwave_heating","K s-1", "rate_of_change_of_air_temperature_due_to_longwave_heating_assuming_clear_sky","K s-1", "tropopause_downward_longwave_heat_flux_density","W m-2", "tropopause_downward_longwave_heat_flux_density_from_above","W m-2", "downward_heat_flux_density_in_sea_ice","W m-2", "downward_heat_flux_density_in_soil","W m-2", "drag_coefficient","1", "derivative_of_wind_speed_wrt_altitude_in_constant_flux_layer","s-1", "downward_stress_in_constant_flux_layer","Pa", "bulk_Richardson_number","1", "upward_sensible_heat_flux_density_in_air","W m-2", "downward_eastward_stress_in_air","Pa", "downward_northward_stress_in_air","Pa", "upward_water_vapour_mass_flux_density_in_air","kg m-2 s-1", "wind_mixing_energy_flux_density_into_sea","W m-2", "surface_upward_sensible_heat_flux_density","W m-2", "surface_downward_sensible_heat_flux_density","W m-2", "surface_upward_sensible_heat_flux_density_from_sea","W m-2", "surface_upward_water_vapour_mass_flux_density","kg m-2 s-1", "surface_upward_latent_heat_flux_density","W m-2", "surface_downward_latent_heat_flux_density","W m-2", "mass_fraction_of_cloud_ice_in_air","1", "atmosphere_cloud_ice_content","kg m-2", "mass_fraction_of_cloud_liquid_water_in_air","1", "atmosphere_cloud_liquid_water_content","kg m-2", "visibility","m", "dew_point_temperature","K", "freezing_temperature_of_sea_water","K", "surface_snow_melt_amount","kg m-2", "surface_snow_melt_heat_flux_density","W m-2", "transpiration_amount","kg m-2", "transpiration_mass_flux_density","kg m-2 s-1", "gross_primary_productivity_of_carbon_amount","kg m-2 s-1", "net_primary_productivity_of_carbon_amount","kg m-2 s-1", "plant_respiration_mass_flux_density","kg m-2 s-1", "large_scale_rainfall_amount","kg m-2", "large_scale_snowfall_amount","kg m-2", "large_scale_rainfall_mass_flux_density","kg m-2 s-1", "large_scale_snowfall_mass_flux_density","kg m-2 s-1", "relative_humidity","1", "convective_rainfall_amount","kg m-2", "convective_snowfall_amount","kg m-2", "rate_of_change_of_specific_humidity_due_to_convection","s-1", "convective_rainfall_mass_flux_density","kg m-2 s-1", "convective_snowfall_mass_flux_density","kg m-2 s-1", "air_pressure_at_convective_cloud_base","Pa", "air_pressure_at_convective_cloud_top","Pa", "mass_fraction_of_convective_condensed_water_in_air","1", "rainfall_mass_flux_density","kg m-2 s-1", "snowfall_mass_flux_density","kg m-2 s-1", "precipitation_mass_flux_density","kg m-2 s-1", "specific_potential_energy","J kg-1", "specific_convectively_available_potential_energy","J kg-1", "precipitation_amount","kg m-2", "large_scale_precipitation_amount","kg m-2", "convective_precipitation_amount","kg m-2", "convective_precipitation_mass_flux_density","kg m-2 s-1", "rate_of_change_of_wind_due_to_convention","m s-2", "rate_of_change_of_specific_humidity_due_to_diabatic_processes","s-1", "rate_of_change_of_air_temperature_due_to_diabatic_processes","s-1", "rate_of_change_of_air_temperature_due_to_large_scale_precipitation","s-1", "rate_of_change_of_air_temperature_due_to_moist_convection","s-1", "rate_of_change_of_air_temperature_due_to_dry_convection","s-1", "surface_eastward_gravity_wave_stress","Pa", "surface_northward_gravity_wave_stress","Pa", "rate_of_change_of_wind_due_to_gravity_wave_drag","m s-2", "rate_of_change_of_eastward_wind_due_to_gravity_wave_drag","m s-2", "rate_of_change_of_northward_wind_due_to_gravity_wave_drag","m s-2", "surface_runoff_amount","kg m-2", "subsurface_runoff_amount","kg m-2", "surface_runoff_mass_flux_density","kg m-2 s-1", "subsurface_runoff_mass_flux_density","kg m-2 s-1", "runoff_mass_flux_density","kg m-2 s-1", "wet_bulb_temperature","K", "omega","Pa s-1", "Ertel_potential_vorticity","K m2 kg-1 s-1", "product_of_eastward_wind_and_northward_wind","m2 s-2", "product_of_air_temperature_and_eastwind_wind","K m s-1", "product_of_air_temperature_and_northward_wind","K m s-1", "square_of_air_temperature","K2", "square_of_eastward_wind","m2 s-2", "square_of_northward_wind","m2 s-2", "product_of_eastward_wind_and_omega","Pa m s-2", "product_of_northward_wind_and_omega","Pa m s-2", "product_of_eastward_wind_and_specific_humidity","m s-1", "product_of_northward_wind_and_specific_humidity","m s-1", "product_of_air_temperature_and_omega","K Pa s-1", "atmosphere_kinetic_energy_content","J m-2", "geopotential_height","m", "geopotential_height_anomaly","m", "product_of_eastward_wind_and_geopotential_height","m2 s-1", "product_of_northward_wind_and_geopotential_height","m2 s-1", "freezing_level_altitude","m", "freezing_level_air_pressure","Pa", "tropopause_air_pressure","Pa", "tropopause_air_temperature","K", "tropopause_altitude","m", "sea_level_air_pressure","Pa", "vegetation_carbon_content","kg m-2", "litter_carbon_mass_flux_density","kg m-2 s-1", "sea_water_temperature","K", "sea_water_potential_temperature","K", "sea_water_salinity","1", "baroclinic_eastward_sea_water_velocity","m s-1", "baroclinic_northward_sea_water_velocity","m s-1", "ocean_barotropic_streamfunction","m3 s-1", "rate_of_change_of_ocean_barotropic_streamfunction","m3 s-2", "sea_surface_elevation","m", "sea_surface_elevation_anomaly","m", "barotropic_eastward_sea_water_velocity","m s-1", "barotropic_northward_sea_water_velocity","m s-1", "ocean_mixed_layer_thickness","m", "eastward_stress_of_sea_ice_on_ocean","Pa", "northward_stress_of_sea_ice_on_ocean","Pa", "surface_snow_thickness_on_sea_ice","m", "upward_sensible_heat_flux_density_in_sea_water_at_sea_ice_base","W m-2", "sea_ice_speed","m s-1", "sea_ice_eastward_velocity","m s-1", "sea_ice_northward_velocity","m s-1", "direction_of_sea_ice_velocity","degree", "divergence_of_sea_ice_velocity","s-1", "rate_of_change_of_sea_ice_thickness_due_to_thermodynamics","m s-1", "surface_downward_eastward_stress","Pa", "surface_downward_northward_stress","Pa", "heat_flux_correction","W m-2", "water_flux_correction","kg m-2 s-1", "ocean_isopycnal_layer_thickness_diffusivity","m2 s-1", "sea_water_upward_velocity","m s-1", "northward_heat_flux_in_ocean","W", "northward_salt_mass_flux_in_ocean","kg s-1", "northward_fresh_water_mass_flux_in_ocean","kg s-1", "significant_height_of_wind_waves_and_swell_waves","m", "direction_of_wind_wave_velocity","degree", "significant_height_of_wind_waves","m", "wind_wave_period","s", "direction_of_swell_wave_velocity","degree", "significant_height_of_swell_waves","m", "swell_wave_period","s"}, new String[] {} ); } catch (Exception e) { System.err.println( "ERROR: " + "Couldn't initialize class visad.data.netcdf.in.CfView: " + e); System.exit(1); } } /** * Constructs from a netCDF dataset and a quantity database. The quantity * database will be supplemented with another database specific to this * view. * * @param netcdf The netCDF dataset. * @param quantDb The default quantity database. * @throws NullPointerException if the netCDF dataset argument is * <code>null</code>. * @throws IllegalArgumentException * if the netCDF dataset doesn't follow the * conventions of this view. */ CfView(Netcdf netcdf, QuantityDB quantDb) { this(netcdf, quantDb, false); } /** * Constructs from a netCDF dataset and a quantity database. The quantity * database will be supplemented with another database specific to this * view. * * @param netcdf The netCDF dataset. * @param quantDb The default quantity database. * @param charToText Convert char variables to Text if true * @throws NullPointerException if the netCDF dataset argument is * <code>null</code>. * @throws IllegalArgumentException * if the netCDF dataset doesn't follow the * conventions of this view. */ CfView(Netcdf netcdf, QuantityDB quantDb, boolean charToText) { super(netcdf, quantDb, charToText); /* * Check the "Conventions" global attribute. */ { String conventions = getConventionsString(netcdf); if (conventions == null) throw new IllegalArgumentException( "No \"Conventions\" attribute in netCDF dataset"); int i; for (i = 0; i < CF_CONVENTIONS_STRINGS.length; i++) if (conventions.equals(CF_CONVENTIONS_STRINGS[i])) break; if (i >= CF_CONVENTIONS_STRINGS.length) throw new IllegalArgumentException( "Illegal \"Conventions\" attribute: \"" + conventions + "\""); } /* * Allocate caches. */ varToRealType = new WeakHashMap(); varToUnitString = new WeakHashMap(); dimsToDomain = new WeakHashMap(); varToAuxCoordVars = new WeakHashMap(); /* * Build a database of all netCDF variables that contain metadata * rather than data. */ auxCoordVars = new TreeSet(varComparator); boundaryVars = new TreeSet(); for (VariableIterator varIter = getNetcdf().iterator(); varIter.hasNext(); ) { Variable var = varIter.next(); Variable[] vars = getAuxCoordVars(var); for (int i = 0; i < vars.length; i++) auxCoordVars.add(vars[i]); Variable boundVar = getBoundaryVar(var); if (boundVar != null) boundaryVars.add(boundVar); } } /** * Returns the "coordinates" attribute of a variable. Returns * <code>null</code> if the variable doesn't have such an attribute. * * @param var The variable. * @return The value of the attribute or * <code>null</code>. * @throws NullPointerException if the variable is <code>null</code>. */ private String getAuxCoordVarString(Variable var) { return getAttributeString(var, "coordinates"); } /** * Indicates if a given variable is a CF auxilliary coordinate variable. * * @param var The variable. * @return <code>true</code> if and only if the * variable is an auxilliary coordinate * variable. */ private boolean isAuxCoordVar(Variable var) { return auxCoordVars.contains(var); } /** * Indicates if a given netCDF variable has any CF auxilliary * coordinate variables. * * @param var The variable. * @return True if and only if the variable has * any auxilliary coordinate variables. * @throw NullPointerException if the variable is <code>null</code>. */ private boolean hasAuxCoordVars(Variable var) { return getAuxCoordVars(var).length > 0; } /** * <p>Returns the CF auxilliary coordinate variables of a given netCDF * variable in netCDF order (outermost dimension first). The returned array * will be empty if the netCDF variable doesn't have any CF auxilliary * coordinate variables.</p> * * <p>This implementation uses {@link #getAuxCoordVarString(Variable)}, * {@link isNumeric(String)}, and {@link #getVariable(String)}. * * @param var The variable. * @return The auxilliary coordinate variables of the * variable. Will have zero length if the * variable doesn't have any. * @throw NullPointerException if the variable is <code>null</code>. */ private Variable[] getAuxCoordVars(Variable var) { if (var == null) throw new NullPointerException(); /* * This implementation caches results to improve performance. */ synchronized(varToAuxCoordVars) { Variable[] vars = (Variable[])varToAuxCoordVars.get(var); if (vars == null) { String attrStr = getAuxCoordVarString(var); if (attrStr == null) { vars = nilVarArray; } else { ArrayList list = new ArrayList(7); for (StringTokenizer st = new StringTokenizer(attrStr); st.hasMoreTokens(); ) { String name = st.nextToken(); // ignore non-mumeric "list" variables if (isNumeric(name)) list.add(getVariable(name)); } vars = (Variable[])list.toArray(nilVarArray); } varToAuxCoordVars.put(var, vars); } return (Variable[])vars.clone(); } } /** * Returns the CF boundary variable of a given netCDF variable or * <code>null</code> if the netCDF variable has no boundary variable. If * the variable referenced by the input variable's <code>bounds</code> * attribute doesn't exist, then a warning message is printed to {@link * System#err} and <code>null</code> is returned. * * @param var The variable. * @return The associated CF boundary variable or * <code>null</code>. * @throw NullPointerException if the variable is <code>null</code>. */ private Variable getBoundaryVar(Variable var) { if (var == null) throw new NullPointerException(); String attrStr = getAttributeString(var, "bounds"); if (attrStr == null) return null; Variable boundVar = getVariable(attrStr); if (boundVar == null) System.err.println( "WARNING: " + "The boundary variable of variable \"" + var.getName() + "\" doesn't exist"); return boundVar; } /** * Indicates if a given variable is a CF boundary variable. * * @param var The variable. * @return <code>true</code> if and only if the * variable is a CF boundary variable. */ private boolean isBoundaryVar(Variable var) { return boundaryVars.contains(var); } /** * Gets the standard name of a netCDF variable from the * <code>long_name</code> attribute. If the attribute doesn't exist * then <code>null</code> is returned. If the the attribute value isn't * a string, then and error message is printed and <code>null</code> is * returned. * * @param var A netCDF variable. * @return The long name of <code>var</code> or * <code>null</code>. */ private String getStandardName(Variable var) { return getAttributeString(var, "standard_name"); } /** * Returns the string value of the unit attribute of a netCDF variable. * Returns <code>null</code> if the unit attribute is missing or invalid. * Because the CF netCDF convention requires the use of the unit attribute, * this method prints a warning message to {@link System#err} if the * variable doesn't have a unit attribute. * * @param var A netCDF variable. * @return The unit of the values of <code>var</code> or * <code>null</code>. */ protected String getUnitString(Variable var) { /* * This method caches results to improve performance and to reduce the * number of warning messages. */ String str; synchronized(varToUnitString) { // NB: A null entry may exist. str = (String)varToUnitString.get(var); if (!varToUnitString.containsKey(var)) { str = super.getUnitString(var); // doesn't print message if (str == null) System.err.println( "WARNING: " + "Variable \"" + var.getName() + "\" doesn't have a unit attribute."); varToUnitString.put(var, str); // cache result } } return str; } /** * <p>Returns the unit of a netCDF variable according to the variable's unit * attribute. Returns <code>null</code> if the unit attribute is missing or * invalid.</p> * * This implementation uses {@link #getUnitString(Variable)} and {@link * View#getUnitFromAttribute(Variable)}. * * @param var A netCDF variable. * @return The unit of <code>var</code> or * <code>null</code>. * @throws NullPointerException if the variable is <code>null</code>. */ protected Unit getUnitFromAttribute(Variable var) { String unitStr = getUnitString(var); if (unitStr == null) return null; if (unitStr.equals("level") || unitStr.equals("layer") || unitStr.equals("sigma_level")) { return CommonUnit.dimensionless; } return super.getUnitFromAttribute(var); } /** * <p>Return the VisAD RealType of a netCDF variable. If the variable is a * type of timestamp and references a non-supported calendar system, then a * warning message is printed to {@link System#err} and an attempt is made * to create a new {@link RealType} with a different name.</p> * * <p>This implementation uses {@link * View#getRealType(Variable)}.</p> * * @param var The netCDF variable. * @return The VisAD RealType of <code>var</code>. * @throws TypeException if a corresponding {@link RealType} needed * to be created but couldn't. */ protected RealType getRealType(Variable var) throws TypeException { RealType type; /* * This method caches results to improve performance and to reduce * the number of warning messages. */ synchronized(varToRealType) { type = (RealType)varToRealType.get(var); if (type == null) { type = getRealTypeFromStandardName(var); if (type != null) varToRealType.put(var, type); // cache result else type = super.getRealType(var); Unit unit = type.getDefaultUnit(); if (unit instanceof OffsetUnit && unit.getAbsoluteUnit().isConvertible(SI.second)) { String str = getAttributeString(var, "calendar"); if (str != null && !str.equals("gregorian") && !str.equals("standard")) { String newName = newName(var); System.err.println( "WARNING: " + "No support for \"" + str + "\" calendar of variable \"" + var + "\". " + "Attempting to create new quantity \"" + newName + "\" with non-timescale unit."); type = RealType.getRealType( newName, unit.getAbsoluteUnit()); varToRealType.put(var, type); // cache result } } } } return type; } /** * If the unit attribute of the variable is inconvertible with the unit of * the variables's standard quantity, then a warning message is printed * to {@link System#err} and an attempt is made to create a new {@link * RealType} with a different name. * * @return The corresponding {@link RealType} or <code>null</code>. */ private RealType getRealTypeFromStandardName(Variable var) { RealType type; String name = getStandardName(var); if (name == null) { type = null; } else { type = cfQuantityDB.get(name); if (type != null) { Unit unit = getUnitFromAttribute(var); if (unit != null && !Unit.canConvert(unit, type.getDefaultUnit())) { String newName = newName(var); System.err.println( "WARNING: " + "The units attribute of variable " + var.getName() + " is incompatible with the unit of the quantity" + " referenced by the standard-name attribute. " + "Attempting to create new quantity \"" + newName + "\"."); type = RealType.getRealType(newName, unit); } } } return type; } /** * Gets an iterator over the virtual VisAD data objects determined by * this view. * * @return An iterator for the virtual VisAD data objects * in the view. */ public VirtualDataIterator getVirtualDataIterator() { return new DataIterator(); } /** * <p>Indicates if a given variable should be ignored during iteration.</p> * * <p>This implementation returns the logical "or" of {@link * #isCoordinateVariable(Variable)}, {@link #isAuxCoordVar(Variable)}, and * {@link #isBoundaryVar(Variable)}.</p> * * @return <code>true</code> if and only if the variable * should be ignored. */ protected boolean isIgnorable(Variable var) { return isCoordinateVariable(var) || isAuxCoordVar(var) || isBoundaryVar(var); } /** * Returns the domain of a netCDF variable. This method supports CF * auxilliary coordinate variables. * * @param var A netCDF variable. * @return The domain of the given variable. * @throws NullPointerException * if the variable is <code>null</code>. * @throws IllegalArgumentException * if the rank of the variable is zero. * @throws TypeException if a {@link RealType} needed to be created but * couldn't. * @throws IOException if a netCDF read-error occurs. */ protected Domain getDomain(Variable var) throws TypeException, IOException { ArrayList list = new ArrayList(7); ArrayList vars = new ArrayList(7); Variable[] auxVars = getAuxCoordVars(var); { Dimension[] dims = getDimensions(var); list = new ArrayList(dims.length + auxVars.length); for (int i = 0; i < dims.length; i++) list.add(new SimpleDimension(dims[i])); } for (int iaux = 0; iaux < auxVars.length; ) { Variable auxVar = auxVars[iaux]; Dimension[] dims = getDimensions(auxVar); vars.add(auxVar); boolean auxVarsHaveUnits = true; for (int j = iaux+1; j < auxVars.length && Arrays.equals(dims, getDimensions(auxVars[j])); j++) { vars.add(auxVars[j]); auxVarsHaveUnits &= getRealType(auxVars[j]).getDefaultUnit() != null; } boolean dimsHaveUnits = true; for (int j = 0; j < dims.length; j++) dimsHaveUnits &= getRealType(dims[j]).getDefaultUnit() != null; /* * Ignore CF auxilliary coordinate variables if they don't * have units and the regular dimensions do because that * probably indicates that the auxilliary coordinate variables * are "alternative coordinates" and that the more important * coordinates are the regular dimensions. Otherwise, favor * auxilliary coordinates variables. */ if (!dimsHaveUnits || auxVarsHaveUnits) { int index = list.indexOf(new SimpleDimension(dims[0])); try { for (int j = 0; j < dims.length; j++) list.remove( list.indexOf(new SimpleDimension(dims[j]))); } catch (IndexOutOfBoundsException e) { throw new IllegalArgumentException( "Invalid dimensional structure: variable \"" + var.getName() + "\""); } list.add( index, new AuxCoordVarsDimension( (Variable[])vars.toArray(nilVarArray))); } iaux += vars.size(); vars.clear(); } /* * This implementation caches results to improve performance. */ DimensionList domain; synchronized (dimsToDomain) { domain = (DimensionList)dimsToDomain.get(list); if (domain == null) { domain = new DimensionList(var, list); // list not copied /* * The clone() method is invoked to ensure that the * DimensionList value in the WeakHashMap doesn't reference * its key. */ dimsToDomain.put(list.clone(), domain); } } return domain; } /** * Iterates over the virtual VisAD data objects in a netCDF dataset * according to the CF conventions. */ final class DataIterator extends VirtualDataIterator { /** * The netCDF variable iterator. */ private final VariableIterator varIter; /** * Constructs from nothing. * * @param view A view of a netCDF dataset. */ DataIterator() { super(CfView.this); varIter = CfView.this.getNetcdf().iterator(); } /** * Returns a clone of the next virtual VisAD data object. * * <p>This implementation uses {@link #isCharToText()}, * {@link #isNumeric(Variable)}, {@link #isIgnorable(Variable)}, * and {@link #getData(Variable)}.</p> * * @return A clone of the next virtual VisAD data * object or <code> null</code> if there is * no more data. * @throws TypeException if a {@link RealType} needed * to be created but couldn't. * @throws VisADException Couldn't create necessary VisAD object. */ protected VirtualData getData() throws TypeException, VisADException, IOException { while (varIter.hasNext()) { Variable var = varIter.next(); // handle text only if charToText == true and rank <= 2 if (!isNumeric(var) && (!isCharToText() || var.getRank() > 2)) continue; // TODO: support arrays of text (Tuple?) if (isIgnorable(var)) { /* * Ignore coordinate variables, auxilliary coordinate * variables, and boundary variables. */ continue; } VirtualScalar scalar = (isNumeric(var) == true) ? (VirtualScalar) new VirtualReal(getRealType(var), var, getRangeSet(var), getUnitFromAttribute(var), getVetter(var)) : (VirtualScalar) new VirtualText(getTextType(var), var); return (var.getRank() == 0 || (!isNumeric(var) && var.getRank() == 1)) ? (VirtualData)scalar : getDomain(var).getVirtualField( new VirtualTuple(scalar)); } return null; // no more data } } /** * The CF domain of a netCDF variable. A CF domain comprises a list of CF * dimensions. A CF dimension is either a netCDF dimension or a list of * auxilliary coordinate variables of the same dimensionality (sequence of * netCDF dimensions). */ private final class DimensionList extends Domain { /** * Outermost dimension first; at least one element. */ private final ArrayList list; private volatile int hashCode; private volatile SampledSet domain; /** * @param var The netCDF variable. * @param list The list of CF dimensions of the * variable. * @throws NullPointerException if the variable or list is * <code>null</code>. * @throws IllegalArgumentException if the rank of the variable is 0 or * if the variable has auxilliaray * coordinate variables whose * dimensional structure is invalid. * @throws TypeException if a {@link RealType} needed to be * created but couldn't. */ DimensionList(Variable var, ArrayList list) throws TypeException { super(var); this.list = list; // NOTE: not copied } /** * Returns a {@link VirtualField} corresponding to this domain and * a given range. * * @param range The range for the {@link VirtualField}. * @throws NullPointerException if the argument is <code>null</code>. * @throws IOException if a read error occurs. * @throws VisADException if a VisAD object can't be created. */ protected VirtualField getVirtualField(VirtualTuple range) throws VisADException, IOException { VirtualField field; int nCfDim = list.size(); if (nCfDim == 1) { field = VirtualField.newVirtualField(getDomainSet(list), range); } else { CfDimension outerDim = (CfDimension)list.get(0); Unit[] units = outerDim.getUnits(); if (nCfDim == 2 && range.getType() instanceof TextType) { //char field = VirtualField.newVirtualField( getDomainSet(list.subList(0,1)), range); } else if (units.length > 1 || !CfView.this.isTime(units[0])) { field = VirtualField.newVirtualField(getDomainSet(list), range); } else { field = VirtualField.newVirtualField( getDomainSet(list.subList(0, 1)), new VirtualTuple( VirtualField.newVirtualField( getDomainSet(list.subList(1, nCfDim)), range))); } } return field; } /** * @throws VisADException if a VisAD object can't be created. */ private SampledSet getDomainSet(List cfDims) throws VisADException, IOException { SampledSet[] sets = new SampledSet[cfDims.size()]; for (int i = 0, j = sets.length; i < sets.length; i++) sets[i] = ((CfDimension)cfDims.get(--j)).getDomainSet(); // reverse order return sets.length == 1 ? sets[0] /* * WORKAROUND: The product() method is invoked * because the VisAD display subsystem has problems * displaying ProductSet-s as of 2001-08-12. */ : new ProductSet(sets).product(); } public boolean equals(Object obj) { if (obj == this) return true; if (!(obj instanceof DimensionList)) return false; return list.equals(((DimensionList)obj).list); } public int hashCode() { int hash = hashCode; if (hash == 0) hash = hashCode = list.hashCode(); return hash; } } private abstract class CfDimension { /** * Units are in netCDF order (unit of outermost dimension first). */ abstract Unit[] getUnits() throws TypeException; abstract SampledSet getDomainSet() throws VisADException, IOException; public abstract boolean equals(Object obj); public abstract int hashCode(); } private final class SimpleDimension extends CfDimension { private final Dimension dim; private transient volatile SampledSet domain; SimpleDimension(Dimension dim) { this.dim = dim; } /** * @throws TypeException if a {@link RealType} needed * to be created but couldn't. */ Unit[] getUnits() throws TypeException { return new Unit[] {getRealType(dim).getDefaultUnit()}; } /** * @throws VisADException if a VisAD object can't be created. */ SampledSet getDomainSet() throws VisADException, IOException { SampledSet set = domain; if (set == null) set = domain = CfView.this.getDomainSet(dim); return set; } public boolean equals(Object obj) { if (obj == this) return true; if (!(obj instanceof SimpleDimension)) return false; return dim.equals(((SimpleDimension)obj).dim); } public int hashCode() { return dim.hashCode(); } } private final class AuxCoordVarsDimension extends CfDimension { /* * Outermost dimension first; at least one element. */ private final Variable[] vars; private transient volatile int hashCode; private transient volatile SampledSet domain; /** * WARNING: It is the responsibility of the client not to modify * the input array. * * @param auxCoordVars Auxilliary coordinate variable in netCDF * order (outermost dimension first). */ AuxCoordVarsDimension(Variable[] auxCoordVars) { if (auxCoordVars.length < 1) throw new IllegalArgumentException(); vars = auxCoordVars; // WARNING: not copied } /** * @throws TypeException if a {@link RealType} needed * to be created but couldn't. */ Unit[] getUnits() throws TypeException { Unit[] units = new Unit[vars.length]; for (int i = 0; i < units.length; i++) units[i] = getRealType(vars[i]).getDefaultUnit(); return units; } /** * @throws TypeException if a {@link RealType} needed * to be created but couldn't. */ SampledSet getDomainSet() throws IOException, TypeException, VisADException { SampledSet set = domain; if (set == null) { Variable var0 = vars[0]; int nDim = var0.getRank(); int[] lengths = var0.getLengths(); // reverse order for (int i = 0, j = nDim; i < nDim/2; i++) { int n = lengths[--j]; lengths[j] = lengths[i]; lengths[i] = n; } int nVar = vars.length; RealType[] types = new RealType[nVar]; Unit[] units = new Unit[nVar]; float[][] values = new float[nVar][]; for (int i = 0, j = nVar; i < nVar; i++) { Variable var = vars[--j]; // reverse order types[i] = getRealType(var); values[i] = toFloat(var.toArray()); units[i] = getUnitFromAttribute(var); } set = domain = GriddedSet.create( nVar == 1 ? (MathType)types[0] : new RealTupleType(types), values, lengths, (CoordinateSystem)null, units, (ErrorEstimate[])null); } return set; } private float[] toFloat(Object obj) { if (obj instanceof byte[]) return toFloat((byte[])obj); if (obj instanceof short[]) return toFloat((short[])obj); if (obj instanceof int[]) return toFloat((int[])obj); if (obj instanceof float[]) return toFloat((float[])obj); return toFloat((double[])obj); } private float[] toFloat(byte[] values) { float[] vals = new float[values.length]; for (int i = 0; i < vals.length; i++) vals[i] = values[i]; return vals; } private float[] toFloat(short[] values) { float[] vals = new float[values.length]; for (int i = 0; i < vals.length; i++) vals[i] = values[i]; return vals; } private float[] toFloat(int[] values) { float[] vals = new float[values.length]; for (int i = 0; i < vals.length; i++) vals[i] = values[i]; return vals; } private float[] toFloat(float[] values) { return values; // WARNING: Not copied } private float[] toFloat(double[] values) { float[] vals = new float[values.length]; for (int i = 0; i < vals.length; i++) vals[i] = (float)values[i]; return vals; } public boolean equals(Object obj) { if (obj == this) return true; if (!(obj instanceof AuxCoordVarsDimension)) return false; AuxCoordVarsDimension that = (AuxCoordVarsDimension)obj; if (vars == that.vars) return true; if (vars.length != that.vars.length) return false; for (int i = 0; i < vars.length; i++) if (!vars[i].getName().equals(that.vars[i].getName())) return false; return true; } public int hashCode() { int hash = hashCode; if (hash == 0) { hash = 0; for (int i = 0; i < vars.length; i++) hash ^= vars[i].getName().hashCode(); hashCode = hash; } return hash; } } }