ucar.nc2.dataset.CoordinateSystem Maven / Gradle / Ivy
/*
* Copyright (c) 1998-2020 John Caron and University Corporation for Atmospheric Research/Unidata
* See LICENSE for license information.
*/
package ucar.nc2.dataset;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Formatter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import ucar.nc2.Dimension;
import ucar.nc2.Dimensions;
import ucar.nc2.Variable;
import ucar.nc2.constants.AxisType;
import ucar.unidata.geoloc.ProjectionImpl;
import ucar.unidata.geoloc.projection.LatLonProjection;
/**
* A CoordinateSystem specifies the coordinates of a Variable's values.
*
* Mathematically it is a vector function F from index space to Sn:
*
*
* F(i,j,k,...) -> (S1, S2, ...Sn)
* where i,j,k are integers, and S is the set of reals (R) or Strings.
*
*
* The components of F are just its coordinate axes:
*
*
* F = (A1, A2, ...An)
* A1(i,j,k,...) -> S1
* A2(i,j,k,...) -> S2
* An(i,j,k,...) -> Sn
*
*
* Concretely, a CoordinateSystem is a set of coordinate axes, and an optional set
* of coordinate transforms.
* The domain rank of F is the number of dimensions it is a function of. The range rank is the number
* of coordinate axes.
*
*
* An important class of CoordinateSystems are georeferencing Coordinate Systems, that locate a
* Variable's values in space and time. A CoordinateSystem that has a Lat and Lon axis, or a GeoX and GeoY
* axis and a Projection CoordinateTransform will have isGeoReferencing() true.
* A CoordinateSystem that has a Height, Pressure, or GeoZ axis will have hasVerticalAxis() true.
*
* Further CoordinateSystems specialization is done by "data type specific" classes such as
* ucar.nc2.ft2.coverage.grid.GridCoordSys.
*
* @author caron
* @see Coordinate System
* Object
* Model
*/
public class CoordinateSystem {
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CoordinateSystem.class);
/**
* Create standard name from list of axes. Sort the axes first
*
* @param axes list of CoordinateAxis
* @return CoordinateSystem name, created from axes names
*/
public static String makeName(List axes) {
List axesSorted = new ArrayList<>(axes);
axesSorted.sort(new CoordinateAxis.AxisComparator());
StringBuilder buff = new StringBuilder();
for (int i = 0; i < axesSorted.size(); i++) {
CoordinateAxis axis = axesSorted.get(i);
if (i > 0)
buff.append(" ");
buff.append(axis.getFullName());
}
return buff.toString();
}
//////////////////////////////////////////////////////////////////////////////////////
/** @deprecated Use CoordinateSystem.builder() */
@Deprecated
protected CoordinateSystem() {}
/**
* Constructor.
*
* @param ds the containing dataset
* @param axes Collection of type CoordinateAxis, must be at least one.
* @param coordTrans Collection of type CoordinateTransform, may be empty or null.
* @deprecated Use CoordinateSystem.builder()
*/
@Deprecated
public CoordinateSystem(NetcdfDataset ds, Collection axes,
Collection coordTrans) {
this.ds = ds;
this.coordAxes = new ArrayList<>(axes);
this.name = makeName(coordAxes);
if (coordTrans != null)
this.coordTrans = new ArrayList<>(coordTrans);
for (CoordinateAxis axis : coordAxes) {
// look for AxisType
AxisType axisType = axis.getAxisType();
if (axisType != null) {
if (axisType == AxisType.GeoX)
xAxis = lesserRank(xAxis, axis);
if (axisType == AxisType.GeoY)
yAxis = lesserRank(yAxis, axis);
if (axisType == AxisType.GeoZ)
zAxis = lesserRank(zAxis, axis);
if (axisType == AxisType.Time)
tAxis = lesserRank(tAxis, axis);
if (axisType == AxisType.Lat)
latAxis = lesserRank(latAxis, axis);
if (axisType == AxisType.Lon)
lonAxis = lesserRank(lonAxis, axis);
if (axisType == AxisType.Height)
hAxis = lesserRank(hAxis, axis);
if (axisType == AxisType.Pressure)
pAxis = lesserRank(pAxis, axis);
if (axisType == AxisType.Ensemble)
ensAxis = lesserRank(ensAxis, axis);
if (axisType == AxisType.RadialAzimuth)
aziAxis = lesserRank(aziAxis, axis);
if (axisType == AxisType.RadialDistance)
radialAxis = lesserRank(radialAxis, axis);
if (axisType == AxisType.RadialElevation)
elevAxis = lesserRank(elevAxis, axis);
}
// collect dimensions
List dims = axis.getDimensionsAll();
domain.addAll(dims);
}
}
// prefer smaller ranks, in case more than one
private CoordinateAxis lesserRank(CoordinateAxis a1, CoordinateAxis a2) {
if (a1 == null)
return a2;
return (a1.getRank() <= a2.getRank()) ? a1 : a2;
}
/**
* add a CoordinateTransform
*
* @param ct add this CoordinateTransform
* @deprecated Use CoordinateSystem.builder()
*/
@Deprecated
public void addCoordinateTransform(CoordinateTransform ct) {
coordTrans.add(ct);
ds.addCoordinateTransform(ct);
}
/**
* add a Collection of CoordinateTransform
*
* @param ct add all CoordinateTransform in this collection
* @deprecated Use CoordinateSystem.builder()
*/
@Deprecated
public void addCoordinateTransforms(Collection ct) {
if (ct != null)
coordTrans.addAll(ct);
}
/** Get the List of CoordinateAxis objects */
public ImmutableList getCoordinateAxes() {
return ImmutableList.copyOf(coordAxes);
}
/** Get the List of CoordinateTransform objects */
public ImmutableList getCoordinateTransforms() {
return ImmutableList.copyOf(coordTrans);
}
/**
* get the name of the Coordinate System
*
* @return the name of the Coordinate System
*/
public String getName() {
return name;
}
/**
* Get the underlying NetcdfDataset
*
* @return the underlying NetcdfDataset.
*/
public NetcdfDataset getNetcdfDataset() {
return ds;
}
/** get the Collection of Dimensions that constitute the domain. */
public ImmutableCollection getDomain() {
return ImmutableList.copyOf(domain);
}
/**
* Get the domain rank of the coordinate system = number of dimensions it is a function of.
*
* @return domain.size()
*/
public int getRankDomain() {
return domain.size();
}
/**
* Get the range rank of the coordinate system = number of coordinate axes.
*
* @return coordAxes.size()
*/
public int getRankRange() {
return coordAxes.size();
}
///////////////////////////////////////////////////////////////////////////
// Convenience routines for finding georeferencing axes
/**
* Find the CoordinateAxis that has the given AxisType.
* If more than one, return the one with lesser rank.
*
* @param type look for this axisType
* @return CoordinateAxis of the given AxisType, else null.
*/
public CoordinateAxis findAxis(AxisType type) {
CoordinateAxis result = null;
for (CoordinateAxis axis : coordAxes) {
AxisType axisType = axis.getAxisType();
if ((axisType != null) && (axisType == type))
result = lesserRank(result, axis);
}
return result;
}
/**
* get the CoordinateAxis with AxisType.GeoX, or null if none.
* if more than one, choose one with smallest rank
*
* @return axis of type AxisType.GeoX, or null if none
*/
public CoordinateAxis getXaxis() {
return xAxis;
}
/**
* get the CoordinateAxis with AxisType.GeoY, or null if none.
* if more than one, choose one with smallest rank
*
* @return axis of type AxisType.GeoY, or null if none
*/
public CoordinateAxis getYaxis() {
return yAxis;
}
/**
* get the CoordinateAxis with AxisType.GeoZ, or null if none.
* if more than one, choose one with smallest rank
*
* @return axis of type AxisType.GeoZ, or null if none
*/
public CoordinateAxis getZaxis() {
return zAxis;
}
/**
* get the CoordinateAxis with AxisType.Time, or null if none.
* if more than one, choose one with smallest rank
*
* @return axis of type AxisType.Time, or null if none
*/
public CoordinateAxis getTaxis() {
return tAxis;
}
/**
* get the CoordinateAxis with AxisType.Lat, or null if none.
* if more than one, choose one with smallest rank
*
* @return axis of type AxisType.Lat, or null if none
*/
public CoordinateAxis getLatAxis() {
return latAxis;
}
/**
* get the CoordinateAxis with AxisType.Lon, or null if none.
* if more than one, choose one with smallest rank *
*
* @return axis of type AxisType.Lon, or null if none
*/
public CoordinateAxis getLonAxis() {
return lonAxis;
}
/**
* get the CoordinateAxis with AxisType.Height, or null if none.
* if more than one, choose one with smallest rank
*
* @return axis of type AxisType.Height, or null if none
*/
public CoordinateAxis getHeightAxis() {
return hAxis;
}
/**
* get the CoordinateAxis with AxisType.Pressure, or null if none.
* if more than one, choose one with smallest rank.
*
* @return axis of type AxisType.Pressure, or null if none
*/
public CoordinateAxis getPressureAxis() {
return pAxis;
}
/**
* get the CoordinateAxis with AxisType.Ensemble, or null if none.
* if more than one, choose one with smallest rank.
*
* @return axis of type AxisType.Ensemble, or null if none
*/
public CoordinateAxis getEnsembleAxis() {
return ensAxis;
}
/**
* get the CoordinateAxis with AxisType.RadialAzimuth, or null if none.
* if more than one, choose one with smallest rank
*
* @return axis of type AxisType.RadialAzimuth, or null if none
*/
public CoordinateAxis getAzimuthAxis() {
return aziAxis;
}
/**
* get the CoordinateAxis with AxisType.RadialDistance, or null if none.
* if more than one, choose one with smallest rank
*
* @return axis of type AxisType.RadialDistance, or null if none
*/
public CoordinateAxis getRadialAxis() {
return radialAxis;
}
/**
* get the CoordinateAxis with AxisType.RadialElevation, or null if none.
* if more than one, choose one with smallest rank
*
* @return axis of type AxisType.RadialElevation, or null if none
*/
public CoordinateAxis getElevationAxis() {
return elevAxis;
}
/**
* Find the first ProjectionCT from the list of CoordinateTransforms.
*
* @return ProjectionCT or null if none.
*/
public ProjectionCT getProjectionCT() {
for (CoordinateTransform ct : coordTrans) {
if (ct instanceof ProjectionCT)
return (ProjectionCT) ct;
}
return null;
}
/**
* Get the Projection for this coordinate system.
* If isLatLon(), then returns a LatLonProjection. Otherwise, extracts the
* projection from any ProjectionCT CoordinateTransform.
*
* @return ProjectionImpl or null if none.
* TODO return Projection in ver6
*/
public ProjectionImpl getProjection() {
if (projection == null) {
if (isLatLon())
projection = new LatLonProjection();
ProjectionCT projCT = getProjectionCT();
if (null != projCT)
projection = projCT.getProjection();
}
return projection;
}
private ProjectionImpl projection;
////////////////////////////////////////////////////////////////////////////
// classification
/**
* true if it has X and Y CoordinateAxis, and a CoordTransform Projection
*
* @return true if it has X and Y CoordinateAxis, and a CoordTransform Projection
*/
public boolean isGeoXY() {
if ((xAxis == null) || (yAxis == null))
return false;
return null != getProjection() && !(projection instanceof LatLonProjection);
}
/**
* true if it has Lat and Lon CoordinateAxis
*
* @return true if it has Lat and Lon CoordinateAxis
*/
public boolean isLatLon() {
return (latAxis != null) && (lonAxis != null);
}
/**
* true if it has radial distance and azimuth CoordinateAxis
*
* @return true if it has radial distance and azimuth CoordinateAxis
*/
public boolean isRadial() {
return (radialAxis != null) && (aziAxis != null);
}
/**
* true if isGeoXY or isLatLon
*
* @return true if isGeoXY or isLatLon
*/
public boolean isGeoReferencing() {
return isGeoXY() || isLatLon();
}
/**
* true if all axes are CoordinateAxis1D
*
* @return true if all axes are CoordinateAxis1D
*/
public boolean isProductSet() {
for (CoordinateAxis axis : coordAxes) {
if (!(axis instanceof CoordinateAxis1D))
return false;
}
return true;
}
/**
* true if all axes are CoordinateAxis1D and are regular
*
* @return true if all axes are CoordinateAxis1D and are regular
*/
public boolean isRegular() {
for (CoordinateAxis axis : coordAxes) {
if (!(axis instanceof CoordinateAxis1D))
return false;
if (!((CoordinateAxis1D) axis).isRegular())
return false;
}
return true;
}
/**
* Check if this Coordinate System is complete for v, ie if all its dimensions are also used by v.
*
* @param v check for this variable
* @return true if all dimensions in V (including parents) are in the domain of this coordinate system.
*/
public boolean isComplete(Variable v) {
return isSubset(Dimensions.makeDimensionsAll(v), domain);
}
/**
* Check if this Coordinate System can be used for the given variable.
* A CoordinateAxis can only be part of a Variable's CoordinateSystem if the CoordinateAxis' set of Dimensions is a
* subset of the Variable's set of Dimensions.
* So, a CoordinateSystem' set of Dimensions must be a subset of the Variable's set of Dimensions.
*
* @param v check for this variable
* @return true if all dimensions in the domain of this coordinate system are in V (including parents).
*/
public boolean isCoordinateSystemFor(Variable v) {
return isSubset(domain, Dimensions.makeDimensionsAll(v));
}
/**
* Test if all the Dimensions in subset are in set
*
* @param subset is this a subset
* @param set of this?
* @return true if all the Dimensions in subset are in set
*/
public static boolean isSubset(Collection subset, Collection set) {
for (Dimension d : subset) {
if (!(set.contains(d)))
return false;
}
return true;
}
public static boolean isSubset(Set subset, Set set) {
for (String d : subset) {
if (!(set.contains(d)))
return false;
}
return true;
}
public static Set makeDomain(Iterable extends Variable> axes) {
Set domain = new HashSet<>();
for (Variable axis : axes) {
domain.addAll(axis.getDimensions());
}
return domain;
}
public static int countDomain(Variable[] axes) {
Set domain = new HashSet<>();
for (Variable axis : axes) {
domain.addAll(axis.getDimensions());
}
return domain.size();
}
/**
* Implicit Coordinate System are constructed based on which Coordinate Variables exist for the Dimensions of the
* Variable.
* This is in contrast to a Coordinate System that is explicitly specified in the file.
*
* @return true if this coordinate system was constructed implicitly.
*/
public boolean isImplicit() {
return isImplicit;
}
/**
* Set whether this Coordinate System is implicit
*
* @param isImplicit true if constructed implicitly.
* @deprecated Use CoordinateSystem.builder()
*/
@Deprecated
protected void setImplicit(boolean isImplicit) {
this.isImplicit = isImplicit;
}
/**
* true if has Height, Pressure, or GeoZ axis
*
* @return true if has a vertical axis
*/
public boolean hasVerticalAxis() {
return (hAxis != null) || (pAxis != null) || (zAxis != null);
}
/**
* true if has Time axis
*
* @return true if has Time axis
*/
public boolean hasTimeAxis() {
return (tAxis != null);
}
/**
* Do we have all the axes in the list?
*
* @param wantAxes List of CoordinateAxis
* @return true if all in our list.
*/
public boolean containsAxes(List wantAxes) {
for (CoordinateAxis ca : wantAxes) {
if (!containsAxis(ca.getFullName()))
return false;
}
return true;
}
/**
* Do we have the named axis?
*
* @param axisName (full unescaped) name of axis
* @return true if we have an axis of that name
*/
public boolean containsAxis(String axisName) {
for (CoordinateAxis ca : coordAxes) {
if (ca.getFullName().equals(axisName))
return true;
}
return false;
}
/**
* Do we have all the dimensions in the list?
*
* @param wantDimensions List of Dimensions
* @return true if all in our list.
*/
public boolean containsDomain(List wantDimensions) {
for (Dimension d : wantDimensions) {
if (!domain.contains(d))
return false;
}
return true;
}
/**
* Do we have all the axes types in the list?
*
* @param wantAxes List of AxisType
* @return true if all in our list.
*/
public boolean containsAxisTypes(List wantAxes) {
for (AxisType wantAxisType : wantAxes) {
if (!containsAxisType(wantAxisType))
return false;
}
return true;
}
/**
* Do we have an axes of the given type?
*
* @param wantAxisType want this AxisType
* @return true if we have at least one axis of that type.
*/
public boolean containsAxisType(AxisType wantAxisType) {
for (CoordinateAxis ca : coordAxes) {
if (ca.getAxisType() == wantAxisType)
return true;
}
return false;
}
/** @deprecated Use CoordinateSystem.builder() */
@Deprecated
void makeTimeAxis() {
if ((tAxis != null) && (tAxis instanceof CoordinateAxis1D) && !(tAxis instanceof CoordinateAxis1DTime)) {
Formatter err = new Formatter();
try {
CoordinateAxis1DTime timeAxis = CoordinateAxis1DTime.factory(ds, tAxis, err);
coordAxes.remove(tAxis);
coordAxes.add(timeAxis);
tAxis = timeAxis;
ds.addCoordinateAxis(timeAxis); // will remove old one
} catch (Exception e) {
log.error(tAxis.getDatasetLocation() + ": Error reading time coord= " + err, e);
}
}
}
////////////////////////////////////////////////////////////////////////////
/**
* Instances which have same name, axes and transforms are equal.
*/
public boolean equals(Object oo) {
if (this == oo)
return true;
if (!(oo instanceof CoordinateSystem))
return false;
CoordinateSystem o = (CoordinateSystem) oo;
if (!getName().equals(o.getName()))
return false;
List oaxes = o.getCoordinateAxes();
for (CoordinateAxis axis : getCoordinateAxes()) {
if (!oaxes.contains(axis))
return false;
}
List otrans = o.getCoordinateTransforms();
for (CoordinateTransform tran : getCoordinateTransforms()) {
if (!otrans.contains(tran))
return false;
}
return true;
}
/** Override Object.hashCode() to implement equals. */
public int hashCode() {
if (hashCode == 0) {
int result = 17;
result = 37 * result + getName().hashCode();
result = 37 * result + getCoordinateAxes().hashCode();
result = 37 * result + getCoordinateTransforms().hashCode();
hashCode = result;
}
return hashCode;
}
private volatile int hashCode;
public String toString() {
return name;
}
////////////////////////////////////////////////////////////////////////////////////////////
// TODO make these final and immutable in 6.
protected NetcdfDataset ds; // needed?
protected List coordAxes = new ArrayList<>();
protected List coordTrans = new ArrayList<>();
// these are calculated
protected String name;
protected Set domain = new HashSet<>(); // set of dimension
protected CoordinateAxis xAxis, yAxis, zAxis, tAxis, latAxis, lonAxis, hAxis, pAxis, ensAxis;
protected CoordinateAxis aziAxis, elevAxis, radialAxis;
protected boolean isImplicit; // where set?
protected String dataType; // Grid, Station, etc. where set?
protected CoordinateSystem(Builder> builder, NetcdfDataset ncd, List axes,
List allTransforms) {
this.ds = ncd;
this.isImplicit = builder.isImplicit;
// find referenced coordinate axes
List axesList = new ArrayList<>();
StringTokenizer stoker = new StringTokenizer(builder.coordAxesNames);
while (stoker.hasMoreTokens()) {
String vname = stoker.nextToken();
for (CoordinateAxis a : axes) {
String aname = a.getFullName();
if (aname.equals(vname)) {
axesList.add(a);
}
}
}
this.coordAxes = axesList;
// calculated
this.name = makeName(coordAxes);
for (CoordinateAxis axis : this.coordAxes) {
// look for AxisType
AxisType axisType = axis.getAxisType();
if (axisType != null) {
if (axisType == AxisType.GeoX)
xAxis = lesserRank(xAxis, axis);
if (axisType == AxisType.GeoY)
yAxis = lesserRank(yAxis, axis);
if (axisType == AxisType.GeoZ)
zAxis = lesserRank(zAxis, axis);
if (axisType == AxisType.Time)
tAxis = lesserRank(tAxis, axis);
if (axisType == AxisType.Lat)
latAxis = lesserRank(latAxis, axis);
if (axisType == AxisType.Lon)
lonAxis = lesserRank(lonAxis, axis);
if (axisType == AxisType.Height)
hAxis = lesserRank(hAxis, axis);
if (axisType == AxisType.Pressure)
pAxis = lesserRank(pAxis, axis);
if (axisType == AxisType.Ensemble)
ensAxis = lesserRank(ensAxis, axis);
if (axisType == AxisType.RadialAzimuth)
aziAxis = lesserRank(aziAxis, axis);
if (axisType == AxisType.RadialDistance)
radialAxis = lesserRank(radialAxis, axis);
if (axisType == AxisType.RadialElevation)
elevAxis = lesserRank(elevAxis, axis);
}
// collect dimensions
List dims = axis.getDimensionsAll();
domain.addAll(dims);
}
// Find the named coordinate transforms in allTransforms.
for (String wantTransName : builder.transNames) {
CoordinateTransform got = allTransforms.stream()
.filter(ct -> (wantTransName.equals(ct.getName())
|| ct.getAttributeContainer() != null && wantTransName.equals(ct.getAttributeContainer().getName())))
.findFirst().orElse(null);
if (got != null) {
coordTrans.add(got);
}
}
}
public Builder> toBuilder() {
return addLocalFieldsToBuilder(builder());
}
// Add local fields to the passed - in builder.
protected Builder> addLocalFieldsToBuilder(Builder extends Builder>> b) {
return b.setImplicit(this.isImplicit).setCoordAxesNames(this.name).addCoordinateTransforms(this.coordTrans);
}
/**
* Get Builder for this class that allows subclassing.
*
* @see "https://community.oracle.com/blogs/emcmanus/2010/10/24/using-builder-pattern-subclasses"
*/
public static Builder> builder() {
return new Builder2();
}
private static class Builder2 extends Builder {
@Override
protected Builder2 self() {
return this;
}
}
public static abstract class Builder> {
public String coordAxesNames; // canonicalized list of names
private List transNames = new ArrayList<>();
private boolean isImplicit;
private boolean built;
protected abstract T self();
// LOOK need to be canonicalized
public T setCoordAxesNames(String names) {
this.coordAxesNames = names;
return self();
}
public T addCoordinateTransformByName(String ct) {
transNames.add(ct);
return self();
}
public T addCoordinateTransforms(Collection axes) {
axes.forEach(axis -> addCoordinateTransformByName(axis.name));
return self();
}
public T setImplicit(boolean isImplicit) {
this.isImplicit = isImplicit;
return self();
}
// LOOK do we really need NetcdfDataset?
public CoordinateSystem build(NetcdfDataset ncd, List axes, List transforms) {
if (built)
throw new IllegalStateException("already built");
built = true;
return new CoordinateSystem(this, ncd, axes, transforms);
}
}
}