/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2010-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2010-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.referencing.adapters;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.io.IOException;
import javax.imageio.IIOException;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.referencing.crs.*;
import ucar.nc2.Dimension;
import ucar.nc2.units.DateUnit;
import ucar.nc2.constants.AxisType;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.dataset.CoordinateAxis;
import ucar.nc2.dataset.CoordinateAxis1D;
import ucar.nc2.dataset.CoordinateSystem;
import ucar.nc2.dataset.CoordinateAxis1DTime;
import org.opengis.metadata.extent.Extent;
import org.opengis.util.InternationalString;
import org.opengis.referencing.operation.Projection;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.cs.TimeCS;
import org.opengis.referencing.cs.VerticalCS;
import org.opengis.referencing.cs.CartesianCS;
import org.opengis.referencing.cs.EllipsoidalCS;
import org.opengis.referencing.crs.CompoundCRS;
import org.opengis.referencing.crs.TemporalCRS;
import org.opengis.referencing.crs.VerticalCRS;
import org.opengis.referencing.crs.ProjectedCRS;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.datum.TemporalDatum;
import org.opengis.referencing.datum.VerticalDatum;
import org.opengis.referencing.datum.GeodeticDatum;
import org.opengis.coverage.grid.GridGeometry;
import org.opengis.coverage.grid.GridEnvelope;
import org.apache.sis.measure.Units;
import org.apache.sis.io.wkt.Formatter;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.image.io.WarningProducer;
import org.geotoolkit.internal.image.io.Warnings;
import org.geotoolkit.internal.image.io.IrregularAxesConverter;
import org.apache.sis.internal.util.UnmodifiableArrayList;
import org.apache.sis.util.ArraysExt;
import org.apache.sis.referencing.datum.DefaultTemporalDatum;
import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
import org.geotoolkit.referencing.cs.DiscreteReferencingFactory;
import org.geotoolkit.referencing.cs.DiscreteCoordinateSystemAxis;
import org.apache.sis.referencing.crs.DefaultProjectedCRS;
import org.geotoolkit.coverage.grid.GeneralGridEnvelope;
import org.geotoolkit.io.wkt.Formattable;
import org.apache.sis.referencing.CommonCRS;
import static java.util.Collections.singletonMap;
import static org.opengis.referencing.IdentifiedObject.NAME_KEY;
import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
/**
* Wraps a NetCDF {@link CoordinateSystem} as an implementation of GeoAPI interfaces.
* This class implements both the GeoAPI {@link org.opengis.referencing.cs.CoordinateSystem} and
* {@link CoordinateReferenceSystem} interfaces because the NetCDF {@code CoordinateSystem}
* object combines the concepts of both of them. It also implements the {@link GridGeometry}
* interface since NetCDF Coordinate Systems contain all information related to the image grid.
*
* {@section Axis order}
* The order of axes returned by {@link #getAxis(int)} is reversed compared to the order of axes
* in the wrapped NetCDF coordinate system. This is because the NetCDF convention stores axes in
* the (<var>time</var>, <var>height</var>, <var>latitude</var>, <var>longitude</var>) order, while
* the Geotk referencing framework typically uses the (<var>longitude</var>, <var>latitude</var>,
* <var>height</var>, <var>time</var>) order.
*
* {@section Regular axes}
* While not mandatory, the Geotk Image I/O framework behaves better if the NetCDF axes
* {@linkplain CoordinateAxis1D#isRegular() are regular}. Irregular axes can sometime be
* made regular by changing the Coordinate Reference System. The {@link #regularize()}
* method attempts to convert some kind of CRS to other kinds of CRS (for example from
* geographic CRS to Mercator projection) and checks if the result is a regular grid.
* <p>
* <b>Example:</b> some NetCDF files contain data computed on a grid which was regular in the
* Mercator projection, but the file declare (<var>longitude</var>, <var>latitude</var>) axes
* for user "convenience", resulting in an irregular latitude axis. Projecting the longitudes
* and latitudes back to the Mercator projection gives back the regular grid.
*
* {@section Restrictions}
* Current implementation has the following restrictions:
* <ul>
* <li><p>At the time of writing, the NetCDF API doesn't specify the CRS datum. Consequently the
* current implementation assumes that all {@code NetcdfCRS} instances use the
* {@linkplain DefaultGeodeticDatum#WGS84 WGS84} geodetic datum.</p></li>
*
* <li><p>This class assumes that the list of NetCDF axes returned by
* {@link CoordinateSystem#getCoordinateAxes()} is stable during the
* lifetime of this {@code NetcdfCRS} instance.</p></li>
* </ul>
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.20
*
* @see org.geotoolkit.image.io.plugin.NetcdfImageReader
*
* @since 3.08
* @module
*/
public class NetcdfCRS extends NetcdfIdentifiedObject implements CoordinateReferenceSystem,
org.opengis.referencing.cs.CoordinateSystem, GridGeometry
{
/**
* The NetCDF coordinate system wrapped by this {@code NetcdfCRS} instance.
*/
private final CoordinateSystem cs;
/**
* The variable dimensions, in the "natural" order (i.e. reverse of NetCDF order). This array
* shall be consistent with the variable dimensions or a sub-set of the variable dimensions,
* which may or may not be the same than the {@linkplain CoordinateSystem#getDomain() CS domain}
* (experience shows that the axis order is sometime different than the variable dimension order).
*
* @see CoordinateSystem#getDomain()
*/
private final Dimension[] domain;
/**
* The NetCDF axes in "natural" order (reverse order compared to NetCDF file).
* May be only a subset of the coordinate system axes.
*
* @see CoordinateSystem#getCoordinateAxes()
*/
private final NetcdfAxis[] axes;
/**
* The grid envelope extent, computed when first needed.
*/
private transient GridEnvelope extent;
/**
* The grid to CRS transform, computed when first needed.
*/
private transient MathTransform gridToCRS;
/**
* Creates a new {@code NetcdfCRS} object wrapping the same NetCDF coordinate system
* than the given object. This copy constructor is provided for subclasses wanting to
* wraps the same NetCDF coordinate system and change a few properties or methods.
*
* @param crs The CRS to copy.
*
* @since 3.15
*/
NetcdfCRS(final NetcdfCRS crs) {
this.cs = crs.cs;
this.domain = crs.domain;
this.axes = crs.axes;
this.extent = crs.extent;
this.gridToCRS = crs.gridToCRS;
}
/**
* Creates a new {@code NetcdfCRS} object wrapping the given NetCDF coordinate system.
* The {@link CoordinateSystem#getCoordinateAxes()} is invoked at construction time.
*
* @param netcdfCS The NetCDF coordinate system to wrap.
* @throws IIOException If the given coordinate system can not be wrapped.
*/
protected NetcdfCRS(final CoordinateSystem netcdfCS) throws IIOException {
this(netcdfCS, getDomain(netcdfCS), netcdfCS.getCoordinateAxes());
}
/**
* Workaround for RFE #4093999 ("Relax constraint on placement of this()/super()
* call in constructors").
*/
private static Dimension[] getDomain(final CoordinateSystem netcdfCS) {
final Dimension[] domain = netcdfCS.getDomain().toArray(new Dimension[netcdfCS.getRankDomain()]);
ArraysExt.reverse(domain);
return domain;
}
/**
* Creates a new {@code NetcdfCRS} object wrapping the given axes of the given NetCDF coordinate system.
*
* @param netcdfCS The NetCDF coordinate system to wrap.
* @param domain Dimensions of the variable for which we are wrapping a coordinate system,
* in natural order (reverse of NetCDF order). They are often, but not necessarily,
* the coordinate system domain except for order.
* @param netcdfAxes The axes to add, in natural order (i.e. reverse of NetCDF order).
* Some axes may be ignored if their domain is not contained in the {@code variableDomain}.
* @throws IIOException If an axis domain is not contained in the given variable domain,
* or if a unit can not be parsed.
*/
NetcdfCRS(final CoordinateSystem netcdfCS, final Dimension[] domain,
final List<CoordinateAxis> netcdfAxes) throws IIOException
{
ensureNonNull("netcdfCS", netcdfCS);
cs = netcdfCS;
this.domain = domain; // No need to clone here.
final int dimension = netcdfAxes.size();
axes = new NetcdfAxis[dimension];
for (int i=0; i<dimension; i++) {
axes[i] = NetcdfAxis.wrap(netcdfAxes.get(i), domain);
}
}
/**
* Creates a new {@code NetcdfCRS} with {@link NetcdfAxis} instances fetched
* from the given components. This is used by the {@link Compound} constructor.
*/
NetcdfCRS(final CoordinateSystem netcdfCS, final Dimension[] domain, final NetcdfCRS... components) {
cs = netcdfCS;
this.domain = domain; // No need to clone here.
final List<NetcdfAxis> netcdfAxes = new ArrayList<>(netcdfCS.getRankRange());
for (final NetcdfCRS c : components) {
netcdfAxes.addAll(Arrays.asList(c.axes));
}
axes = netcdfAxes.toArray(new NetcdfAxis[netcdfAxes.size()]);
}
/**
* Converts irregular axes to regular ones, if possible. If this CRS contains a geographic
* component, and if the (<var>longitude</var>, <var>latitude</var>) axes of that component
* are irregular, then this method will try to project the axes to the Mercator projection
* and see if the result {@linkplain CoordinateAxis1D#isRegular() is regular}. In such case,
* a new CRS with those regular axes is built and returned.
* <p>
* If this method can not improve the axes regularity, then this method returns {@code this}.
*
* @return A CRS with potentially some axes made regular, or {@code this}.
*
* @see org.geotoolkit.referencing.cs.DiscreteReferencingFactory
*
* @since 3.15
*/
public CoordinateReferenceSystem regularize() {
// Actual implementation is provided by subclasses.
return this;
}
/**
* Returns the wrapped NetCDF coordinate system.
* <p>
* <b>Note:</b> The dimension of the returned NetCDF Coordinate System may be greater than the
* dimension of the GeoAPI CRS implemented by this object, because the NetCDF CS puts all axes
* in a single object while the GeoAPI CRS may splits the axes in various kind of CRS
* ({@link GeographicCRS}, {@link VerticalCRS}, {@link TemporalCRS}).
*/
@Override
public CoordinateSystem delegate() {
return cs;
}
/**
* Returns the coordinate system name. The default implementation delegates to
* {@link CoordinateSystem#getName()}.
*
* @see CoordinateSystem#getName()
*/
@Override
public String getCode() {
return cs.getName();
}
/**
* Returns the number of dimensions.
*
* @see CoordinateSystem#getRankRange()
*/
@Override
public int getDimension() {
return axes.length;
}
/**
* Returns the coordinate system, which is {@code this}.
*/
@Override
public org.opengis.referencing.cs.CoordinateSystem getCoordinateSystem() {
return this;
}
/**
* Returns the axis at the given dimension. Note that the order of axes returned by this
* method is reversed compared to the order of axes in the NetCDF coordinate system. See
* the <a href="#skip-navbar_top">class javadoc</a> for more information.
*
* @param dimension The zero based index of axis.
* @return The axis at the specified dimension.
* @throws IndexOutOfBoundsException if {@code dimension} is out of bounds.
*
* @see CoordinateSystem#getCoordinateAxes()
*/
@Override
public NetcdfAxis getAxis(final int dimension) throws IndexOutOfBoundsException {
return axes[dimension];
}
/**
* Returns the valid coordinate range of the NetCDF grid coordinates.
* The lowest valid grid coordinate is zero.
*
* @return The valid coordinate range of a grid coverage.
*
* @since 3.20 (derived from 3.09)
*/
@Override
public synchronized GridEnvelope getExtent() {
return getGridRange();
}
/**
* @deprecated Renamed {@link #getExtent()}.
*/
@Override
@Deprecated
public synchronized GridEnvelope getGridRange() {
if (extent == null) {
int i = domain.length;
final int[] lower = new int[i];
final int[] upper = new int[i];
while (--i >= 0) {
upper[i] = domain[i].getLength();
}
extent = new GeneralGridEnvelope(lower, upper, false);
}
return extent;
}
/**
* Returns the transform from grid coordinates to this CRS coordinates.
* The returned transform is often specialized in two ways:
* <p>
* <ul>
* <li>If the underlying NetCDF coordinate system {@linkplain CoordinateSystem#isRegular()
* is regular}, then the returned transform implements the
* {@link org.apache.sis.referencing.operation.transform.LinearTransform} interface.</li>
* <li>If this CRS is regular and two-dimensional, then the returned transform is also an
* instance of Java2D {@link java.awt.geom.AffineTransform}.</li>
* </ul>
*
* @return The transform from grid to this CRS.
*/
@Override
public synchronized MathTransform getGridToCRS() {
if (gridToCRS == null) {
gridToCRS = NetcdfGridToCRS.create(domain, axes);
}
return gridToCRS;
}
/**
* Returns {@code null} since NetCDF coordinate systems don't specify their domain
* of validity.
*/
@Override
public Extent getDomainOfValidity() {
return null;
}
/**
* Returns {@code null} since NetCDF coordinate systems don't specify their scope.
*/
@Override
public InternationalString getScope() {
return null;
}
/**
* The CRS for compound CRS.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.15
*
* @since 3.14
* @module
*/
static final class Compound extends NetcdfCRS implements CompoundCRS,
org.opengis.referencing.cs.CoordinateSystem, Formattable
{
/**
* The components of this compound CRS.
*/
private final List<CoordinateReferenceSystem> components;
/**
* Wraps the given coordinate system.
*/
Compound(final CoordinateSystem cs, final Dimension[] domain, final NetcdfCRS[] components) {
super(cs, domain, components);
this.components = UnmodifiableArrayList.<CoordinateReferenceSystem>wrap(components);
}
/**
* Wraps the same coordinate system than the given CRS, with different components.
*/
private Compound(final Compound crs, final CoordinateReferenceSystem[] components) {
super(crs);
this.components = UnmodifiableArrayList.wrap(components);
}
/**
* For each components, tries to make them regular.
*/
@Override
public CoordinateReferenceSystem regularize() {
final CoordinateReferenceSystem[] regular = new CoordinateReferenceSystem[components.size()];
boolean changed = false;
for (int i=0; i<regular.length; i++) {
final NetcdfCRS old = (NetcdfCRS) components.get(i);
changed |= ((regular[i] = old.regularize()) != old);
}
if (changed) {
final double[][] ordinates = new double[getDimension()][]; // Null elements are okay.
return DiscreteReferencingFactory.createDiscreteCRS(new Compound(this, regular), ordinates);
}
return super.regularize();
}
/**
* Returns the coordinate system, which is {@code this}.
*/
@Override
public org.opengis.referencing.cs.CoordinateSystem getCoordinateSystem() {
return this;
}
/**
* Returns the components of this compound CRS.
*/
@Override
public List<CoordinateReferenceSystem> getComponents() {
return components;
}
/**
* Delegates to the Geotk formatting code.
*/
@Override
public String formatTo(final Formatter formatter) {
final CompoundCRS regularized = (CompoundCRS) this.regularize();
final CoordinateReferenceSystem[] systems = new CoordinateReferenceSystem[regularized.getComponents().size()];
int i = 0;
for (final CoordinateReferenceSystem component : regularized.getComponents()) {
systems[i++] = AbstractCRS.castOrCopy(component);
}
return new DefaultCompoundCRS(IdentifiedObjects.getProperties(regularized), systems) {
@Override public String formatTo(final Formatter formatter) {
return super.formatTo(formatter);
}
}.formatTo(formatter);
}
}
/**
* The CRS for temporal coordinates.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.14
*
* @since 3.14
* @module
*/
static final class Temporal extends NetcdfCRS implements TemporalCRS, TimeCS, Formattable {
/**
* The temporal datum.
*/
private final TemporalDatum datum;
/**
* Wraps the given coordinate system.
*/
Temporal(final CoordinateSystem cs, final Dimension[] domain, final CoordinateAxis netcdfAxis) throws IIOException {
super(cs, domain, Collections.singletonList(netcdfAxis));
final String unitSymbol = netcdfAxis.getUnitsString();
final DateUnit unit;
try {
unit = new DateUnit(unitSymbol);
} catch (Exception e) {
throw new IIOException(Errors.format(Errors.Keys.UnknownUnit_1, unitSymbol), e);
}
datum = new DefaultTemporalDatum(singletonMap(NAME_KEY, unitSymbol), unit.getDateOrigin());
getAxis(0).unit = Units.SECOND.multiply(unit.getTimeUnit().getValueInSeconds());
}
/**
* If the given axis is not an instance of {@link CoordinateAxis1DTime}, tries to build
* a {@code CoordinateAxis1DTime} now. Otherwise returns the axis unchanged. This method
* can be invoked before to pass the axis to the constructor, if desired.
*
* @param axis The axis to check.
* @param file The originating dataset, or {@code null} if none.
* @param logger An optional object where to log warnings, or {@code null} if none.
* @return The axis as an (@link CoordinateAxis1DTime} if possible.
* @throws IOException If an I/O operation was needed and failed.
*/
static CoordinateAxis complete(CoordinateAxis axis, final NetcdfDataset file,
final WarningProducer logger) throws IOException
{
if (!(axis instanceof CoordinateAxis1DTime) && file != null) {
final java.util.Formatter formatter = (logger != null) ? new java.util.Formatter() : null;
axis = CoordinateAxis1DTime.factory(file, axis, formatter);
if (formatter != null) {
final StringBuilder buffer = (StringBuilder) formatter.out();
if (buffer.length() != 0) {
Warnings.log(logger, null, NetcdfCRS.class, "wrap", buffer.toString());
}
}
}
return axis;
}
/**
* Returns the coordinate system, which is {@code this}.
*/
@Override
public TimeCS getCoordinateSystem() {
return this;
}
/**
* Returns the datum.
*/
@Override
public TemporalDatum getDatum() {
return datum;
}
/**
* Delegates to the Geotk formatting code.
*/
@Override
public String formatTo(final Formatter formatter) {
return new DefaultTemporalCRS(this) {
@Override public String formatTo(final Formatter formatter) {
return super.formatTo(formatter);
}
}.formatTo(formatter);
}
}
/**
* The CRS for vertical coordinates.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.14
*
* @since 3.14
* @module
*/
static final class Vertical extends NetcdfCRS implements VerticalCRS, VerticalCS, Formattable {
/**
* The vertical datum.
*/
private final VerticalDatum datum;
/**
* Wraps the given coordinate system.
*/
Vertical(final CoordinateSystem cs, final Dimension[] domain, final CoordinateAxis netcdfAxis) throws IIOException {
super(cs, domain, Collections.singletonList(netcdfAxis));
switch (netcdfAxis.getAxisType()) {
case Pressure: datum = CommonCRS.Vertical.BAROMETRIC .datum(); break;
case Height: datum = CommonCRS.Vertical.MEAN_SEA_LEVEL.datum(); break;
case GeoZ: datum = CommonCRS.Vertical.ELLIPSOIDAL .datum(); break;
default: datum = CommonCRS.Vertical.OTHER_SURFACE .datum(); break;
}
}
/**
* Returns the coordinate system, which is {@code this}.
*/
@Override
public VerticalCS getCoordinateSystem() {
return this;
}
/**
* Returns the datum.
*/
@Override
public VerticalDatum getDatum() {
return datum;
}
/**
* Delegates to the Geotk formatting code.
*/
@Override
public String formatTo(final Formatter formatter) {
return new DefaultVerticalCRS(this) {
@Override public String formatTo(final Formatter formatter) {
return super.formatTo(formatter);
}
}.formatTo(formatter);
}
}
/**
* The CRS for geographic coordinates. This is normally a two-dimensional CRS (current
* {@link NetcdfCRS} implementation has no support for 3D geographic CRS). However a
* different dimension (either 1 or more than 2) may happen for unusual NetCDF files.
* <p>
* This class assumes that the geodetic datum is {@linkplain DefaultGeodeticDatum#WGS84 WGS84}.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.15
*
* @since 3.08
* @module
*/
static final class Geographic extends NetcdfCRS implements GeographicCRS, EllipsoidalCS, Formattable {
/**
* Wraps the given coordinate system. The given list of axes should in theory contains
* exactly 2 elements (current {@link NetcdfCRS} implementation has no support for 3D
* geographic CRS). However a different number of axes may be provided if the
* {@link NetcdfCRS#wrap(CoordinateSystem)} method has been unable to split the
* NetCDF coordinate system into geodetic, vertical and temporal components.
*/
Geographic(final CoordinateSystem cs, final Dimension[] domain, final List<CoordinateAxis> netcdfAxis) throws IIOException {
super(cs, domain, netcdfAxis);
}
/**
* If the axes of this geographic CRS are irregular, tries to project them to the
* Mercator projection. If there is any axis that are not latitude or longitude
* (which should not be the case), then those axes are lost.
*/
@Override
public CoordinateReferenceSystem regularize() {
NetcdfAxis latitude = null, longitude = null;
for (int i=getDimension(); --i>=0;) {
final NetcdfAxis axis = getAxis(i);
final AxisType type = axis.delegate().getAxisType();
if (type != null) switch (type) {
case Lat: latitude = axis; break;
case Lon: longitude = axis; break;
}
}
if ((latitude instanceof DiscreteCoordinateSystemAxis<?>) &&
(longitude instanceof DiscreteCoordinateSystemAxis<?>) &&
(!latitude.isRegular() || !longitude.isRegular()))
{
/*
* The 1E-4 threshold have been determined empirically from the IFREMER Coriolis
* data. Note that the threshold used by the NetCDF library version 4.1 in the
* CoordinateSystem1D.isRegular() method is 5E-3.
*/
final IrregularAxesConverter converter = new IrregularAxesConverter(1E-4, null);
final ProjectedCRS crs = converter.canConvert(
(DiscreteCoordinateSystemAxis<?>) longitude,
(DiscreteCoordinateSystemAxis<?>) latitude);
if (crs != null) {
return crs;
}
}
return super.regularize();
}
/**
* Returns the coordinate system, which is {@code this}.
*/
@Override
public EllipsoidalCS getCoordinateSystem() {
return this;
}
/**
* Returns the datum, which is assumed WGS84.
*/
@Override
public GeodeticDatum getDatum() {
return CommonCRS.WGS84.datum();
}
/**
* Delegates to the Geotk formatting code.
*/
@Override
public String formatTo(final Formatter formatter) {
return new DefaultGeographicCRS(this) {
@Override public String formatTo(Formatter formatter) {
return super.formatTo(formatter);
}
}.formatTo(formatter);
}
}
/**
* The CRS for projected coordinates. This is normally a two-dimensional CRS. However
* a different dimension (either 1 or more than 2) may happen for unusual NetCDF files.
* <p>
* This class assumes that the geodetic datum is {@linkplain DefaultGeodeticDatum#WGS84 WGS84}.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.20
*
* @since 3.08
* @module
*/
static final class Projected extends NetcdfCRS implements ProjectedCRS, CartesianCS, Formattable {
/**
* The NetCDF projection, or {@code null} if none.
* Will be created when first needed.
*/
private transient Projection projection;
/**
* Wraps the given coordinate system. The given list of axes should in theory contains
* exactly 2 elements. However a different number of axes may be provided if the
* {@link NetcdfCRS#wrap(CoordinateSystem)} method has been unable to split the NetCDF
* coordinate system into geodetic, vertical and temporal components.
*/
Projected(final CoordinateSystem cs, final Dimension[] domain, final List<CoordinateAxis> netcdfAxis) throws IIOException {
super(cs, domain, netcdfAxis);
}
/**
* Returns the coordinate system, which is {@code this}.
*/
@Override
public CartesianCS getCoordinateSystem() {
return this;
}
/**
* Returns the datum, which is assumed the {@linkplain DefaultGeodeticDatum#WGS84 WGS84}.
* This datum must be the same than the datum of the CRS returned by {@link #getBaseCRS()}.
*/
@Override
public GeodeticDatum getDatum() {
return CommonCRS.WGS84.datum();
}
/**
* Returns the base CRS, which is assumed {@linkplain DefaultGeographicCRS#SPHERE sphere}.
* We presume a sphere rather than WGS84 because the NetCDF projection framework uses
* spherical formulas.
*/
@Override
public GeographicCRS getBaseCRS() {
return CommonCRS.SPHERE.normalizedGeographic();
}
/**
* Returns a wrapper around the NetCDF projection.
*
* @throws IllegalStateException If the NetCDF coordinate system does not define a projection.
*/
@Override
public synchronized Projection getConversionFromBase() {
if (projection == null) {
final ucar.unidata.geoloc.Projection p = delegate().getProjection();
if (p == null) {
throw new IllegalStateException(Errors.format(Errors.Keys.UnspecifiedTransform));
}
projection = new NetcdfProjection(p, getBaseCRS(), this);
}
return projection;
}
/**
* Delegates to the Geotk formatting code.
*/
@Override
public String formatTo(final Formatter formatter) {
return new DefaultProjectedCRS(this) {
@Override public String formatTo(final Formatter formatter) {
return super.formatTo(formatter);
}
}.formatTo(formatter);
}
}
}