/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2011-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2011-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.Collections;
import java.io.Serializable;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.Formula;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.operation.OperationMethod;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterValueGroup;
import org.geotoolkit.referencing.operation.transform.AbstractMathTransform;
import org.apache.sis.referencing.operation.transform.IterationStrategy;
import org.geotoolkit.parameter.ParameterGroup;
import org.geotoolkit.resources.Errors;
import ucar.unidata.util.Parameter;
import ucar.unidata.geoloc.LatLonPoint;
import ucar.unidata.geoloc.LatLonPointImpl;
import ucar.unidata.geoloc.Projection;
import ucar.unidata.geoloc.ProjectionPoint;
import ucar.unidata.geoloc.ProjectionPointImpl;
/**
* Wraps a NetCDF {@link Projection} object in a GeoAPI {@link MathTransform}.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.20
*
* @since 3.20
* @module
*/
final class NetcdfTransform extends AbstractMathTransform implements MathTransform2D, Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 2958893266028937128L;
/**
* The NetCDF projection specified at construction time.
*/
final Projection projection;
/**
* {@code true} if this math transform is for the inverse projection.
*/
private final boolean isInverse;
/**
* The inverse of this math transform, or {@code null} if not yet computed.
* Will be created by {@link #inverse()} when first needed.
*/
private transient MathTransform2D inverse;
/**
* Creates a new wrapper for the given NetCDF projection object.
*
* @param projection The NetCDF projection.
*/
NetcdfTransform(final Projection projection) {
this.projection = projection;
this.isInverse = false;
}
/**
* Creates a new wrapper as the inverse of the given projection.
*/
private NetcdfTransform(final NetcdfTransform other) {
projection = other.projection;
isInverse = !other.isInverse;
inverse = other;
}
/**
* Returns the number of source dimensions.
* In current implementation, the number of dimensions is fixed to 2.
*/
@Override
public int getSourceDimensions() {
return 2;
}
/**
* Returns the number of target dimensions.
* In current implementation, the number of dimensions is fixed to 2.
*/
@Override
public int getTargetDimensions() {
return 2;
}
/**
* Transforms a single point. This method does not support derivative calculation. The
* coordinate transformation is delegated to {@link #transform(double[], int, double[],
* int, int)}.
*/
@Override
public Matrix transform(final double[] srcPts, final int srcOff,
final double[] dstPts, final int dstOff, boolean derivate)
throws TransformException
{
if (derivate) {
throw new TransformException(Errors.format(Errors.Keys.CantComputeDerivative));
}
transform(srcPts, srcOff, dstPts, dstOff, 1);
return null;
}
/**
* Transforms an arbitrary amount of points from the given source array to the given
* destination array. This method delegates to one of the following methods for each
* point:
* <p>
* <ul>
* <li>{@link Projection#latLonToProj(LatLonPoint, ProjectionPointImpl)}
* if {@link #isInverse} is {@code false}.</li>
* <li>{@link Projection#projToLatLon(ProjectionPoint, LatLonPointImpl)}
* if {@link #isInverse} is {@code true}.</li>
* </ul>
*/
@Override
public void transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts)
throws TransformException
{
double[] dstFinal = null;
int offFinal = 0;
int srcInc = getSourceDimensions();
int dstInc = getTargetDimensions();
if (srcPts == dstPts) {
switch (IterationStrategy.suggest(srcOff, srcInc, dstOff, dstInc, numPts)) {
case ASCENDING: {
break;
}
case DESCENDING: {
srcOff += (numPts-1) * srcInc; srcInc = -srcInc;
dstOff += (numPts-1) * dstInc; dstInc = -dstInc;
break;
}
default: // Following should alway work even for unknown cases.
case BUFFER_SOURCE: {
srcPts = Arrays.copyOfRange(srcPts, srcOff, srcOff + numPts*srcInc);
srcOff = 0;
break;
}
case BUFFER_TARGET: {
dstFinal = dstPts; dstPts = new double[numPts * dstInc];
offFinal = dstOff; dstOff = 0;
break;
}
}
}
final LatLonPointImpl src = new LatLonPointImpl();
final ProjectionPointImpl dst = new ProjectionPointImpl();
while (--numPts >= 0) {
if (isInverse) {
dst.setLocation(srcPts[srcOff], srcPts[srcOff+1]);
final LatLonPoint pt = projection.projToLatLon(dst, src);
dstPts[dstOff] = pt.getLongitude();
dstPts[dstOff+1] = pt.getLatitude();
} else {
src.set(srcPts[srcOff+1], srcPts[srcOff]); // (lat,lon)
final ProjectionPoint pt = projection.latLonToProj(src, dst);
dstPts[dstOff ] = pt.getX();
dstPts[dstOff+1] = pt.getY();
}
srcOff += srcInc;
dstOff += dstInc;
}
if (dstFinal != null) {
System.arraycopy(dstPts, 0, dstFinal, offFinal, dstPts.length);
}
}
/**
* Returns the inverse of this math transform.
*/
@Override
public synchronized MathTransform2D inverse() {
if (inverse == null) {
inverse = new NetcdfTransform(this);
}
return inverse;
}
/**
* The operation method returned by {@link NetcdfProjection#getMethod()}. The main purpose of
* this implementation is to provide access to the {@link Projection#getClassName()} method.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.20
*
* @since 3.20
* @module
*/
final class Method extends NetcdfIdentifiedObject implements OperationMethod, Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 4205933145793084934L;
/**
* Returns the NetCDF projection wrapped by this adapter.
*
* @return The NetCDF projection object.
*/
@Override
public Projection delegate() {
return projection;
}
/**
* Returns the map projection class name, for example "<cite>Transverse Mercator</cite>".
*
* @see Projection#getClassName()
*/
@Override
public String getCode() {
return projection.getClassName();
}
/**
* Not yet implemented.
*/
@Override
public Formula getFormula() {
return null;
}
/**
* Returns the number of {@linkplain MathTransform#getSourceDimensions() source dimensions}
* of the math transform.
*/
@Override
public Integer getSourceDimensions() {
return NetcdfTransform.this.getSourceDimensions();
}
/**
* Returns the number of {@linkplain MathTransform#getTargetDimensions() target dimensions}
* of the math transform.
*/
@Override
public Integer getTargetDimensions() {
return NetcdfTransform.this.getTargetDimensions();
}
/**
* Returns the descriptor of the math transform parameters.
*/
@Override
public ParameterDescriptorGroup getParameters() {
return getParameterValues().getDescriptor();
}
}
/**
* Wraps the NetCDF parameters in a GeoAPI parameter object. This method returns
* a wrapper around the NetCDF {@link Parameter} objects.
*
* @see Projection#getProjectionParameters()
*/
@Override
public ParameterValueGroup getParameterValues() {
final List<Parameter> param = projection.getProjectionParameters();
final NetcdfParameter<?>[] values = new NetcdfParameter<?>[param.size()];
for (int i=0; i<values.length; i++) {
values[i] = new NetcdfParameter<>(param.get(i));
}
return new ParameterGroup(Collections.singletonMap(
ParameterDescriptorGroup.NAME_KEY, projection.getClassName()), values);
}
}