/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, availible at the root
* application directory.
*/
package org.vfny.geoserver.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Logger;
import javax.media.jai.BorderExtender;
import javax.media.jai.Interpolation;
import javax.media.jai.InterpolationNearest;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.processing.DefaultProcessor;
import org.geotools.coverage.processing.operation.Crop;
import org.geotools.coverage.processing.operation.FilteredSubsample;
import org.geotools.coverage.processing.operation.Interpolate;
import org.geotools.coverage.processing.operation.Resample;
import org.geotools.coverage.processing.operation.Scale;
import org.geotools.coverage.processing.operation.SelectSampleDimension;
import org.geotools.factory.Hints;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.referencing.CRS;
import org.opengis.coverage.Coverage;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.vfny.geoserver.wcs.WcsException;
/**
*
* @author Simone Giannecchini, GeoSolutions
* @author Alessio Fabiani, GeoSolutions
*
*/
public class WCSUtils {
private final static Hints LENIENT_HINT = new Hints(Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE);
private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.vfny.geoserver.util");
private final static SelectSampleDimension bandSelectFactory = new SelectSampleDimension();
private final static Crop cropFactory = new Crop();
private final static Interpolate interpolateFactory = new Interpolate();
private final static Scale scaleFactory = new Scale();
private final static FilteredSubsample filteredSubsampleFactory = new FilteredSubsample();
private final static Resample resampleFactory = new Resample();
static {
// ///////////////////////////////////////////////////////////////////
//
// Static Processors
//
// ///////////////////////////////////////////////////////////////////
final DefaultProcessor processor = new DefaultProcessor(LENIENT_HINT);
bandSelectParams = processor.getOperation("SelectSampleDimension").getParameters();
cropParams = processor.getOperation("CoverageCrop").getParameters();
interpolateParams = processor.getOperation("Interpolate").getParameters();
scaleParams = processor.getOperation("Scale").getParameters();
resampleParams = processor.getOperation("Resample").getParameters();
filteredSubsampleParams = processor.getOperation("FilteredSubsample").getParameters();
}
private final static ParameterValueGroup bandSelectParams;
private final static ParameterValueGroup cropParams;
private final static ParameterValueGroup interpolateParams;
private final static ParameterValueGroup resampleParams;
private final static ParameterValueGroup scaleParams;
private final static ParameterValueGroup filteredSubsampleParams;
private final static Hints hints = new Hints(new HashMap(5));
static {
hints.add(LENIENT_HINT);
}
/**
* <strong>Reprojecting</strong><br>
* The new grid geometry can have a different coordinate reference system
* than the underlying grid geometry. For example, a grid coverage can be
* reprojected from a geodetic coordinate reference system to Universal
* Transverse Mercator CRS.
*
* @param coverage
* GridCoverage2D
* @param sourceCRS
* CoordinateReferenceSystem
* @param targetCRS
* CoordinateReferenceSystem
* @return GridCoverage2D
* @throws WcsException
*/
public static GridCoverage2D reproject(GridCoverage2D coverage,
final CoordinateReferenceSystem sourceCRS, final CoordinateReferenceSystem targetCRS,
final Interpolation interpolation) throws WcsException {
// ///////////////////////////////////////////////////////////////////
//
// REPROJECT
//
//
// ///////////////////////////////////////////////////////////////////
if (!CRS.equalsIgnoreMetadata(sourceCRS, targetCRS)) {
/*
* Operations.DEFAULT.resample( coverage, targetCRS, null,
* Interpolation.getInstance(Interpolation.INTERP_NEAREST))
*/
final ParameterValueGroup param = (ParameterValueGroup) resampleParams.clone();
param.parameter("Source").setValue(coverage);
param.parameter("CoordinateReferenceSystem").setValue(targetCRS);
param.parameter("GridGeometry").setValue(null);
param.parameter("InterpolationType").setValue(interpolation);
coverage = (GridCoverage2D) resampleFactory.doOperation(param, hints);
}
return coverage;
}
/**
* <strong>Interpolating</strong><br>
* Specifies the interpolation type to be used to interpolate values for
* points which fall between grid cells. The default value is nearest
* neighbor. The new interpolation type operates on all sample dimensions.
* Possible values for type are: {@code "NearestNeighbor"},
* {@code "Bilinear"} and {@code "Bicubic"} (the {@code "Optimal"}
* interpolation type is currently not supported).
*
* @param coverage
* GridCoverage2D
* @param interpolation
* Interpolation
* @return GridCoverage2D
* @throws WcsException
*/
public static GridCoverage2D interpolate(GridCoverage2D coverage,
final Interpolation interpolation) throws WcsException {
// ///////////////////////////////////////////////////////////////////
//
// INTERPOLATE
//
//
// ///////////////////////////////////////////////////////////////////
if (interpolation != null) {
/* Operations.DEFAULT.interpolate(coverage, interpolation) */
final ParameterValueGroup param = (ParameterValueGroup) interpolateParams.clone();
param.parameter("Source").setValue(coverage);
param.parameter("Type").setValue(interpolation);
coverage = (GridCoverage2D) interpolateFactory.doOperation(param, hints);
}
return coverage;
}
/**
* <strong>Scaling</strong><br>
* Let user to scale down to the EXACT needed resolution. This step does not
* prevent from having loaded an overview of the original image based on the
* requested scale.
*
* @param coverage
* GridCoverage2D
* @param newGridRange
* GridRange
* @param sourceCoverage
* GridCoverage
* @param sourceCRS
* CoordinateReferenceSystem
* @param destinationEnvelopeInSourceCRS
* @return GridCoverage2D
*/
public static GridCoverage2D scale(final GridCoverage2D coverage, final GridEnvelope newGridRange,
final GridCoverage sourceCoverage, final CoordinateReferenceSystem sourceCRS,
final GeneralEnvelope destinationEnvelopeInSourceCRS) {
// ///////////////////////////////////////////////////////////////////
//
// SCALE to the needed resolution
// Let me now scale down to the EXACT needed resolution. This step does
// not prevent from having loaded an overview of the original image
// based on the requested scale.
//
// ///////////////////////////////////////////////////////////////////
GridGeometry2D scaledGridGeometry = new GridGeometry2D(newGridRange,
(destinationEnvelopeInSourceCRS != null) ? destinationEnvelopeInSourceCRS
: sourceCoverage.getEnvelope());
/*
* Operations.DEFAULT.resample( coverage, sourceCRS, scaledGridGeometry,
* Interpolation.getInstance(Interpolation.INTERP_NEAREST));
*/
final ParameterValueGroup param = (ParameterValueGroup) resampleParams.clone();
param.parameter("Source").setValue(coverage);
param.parameter("CoordinateReferenceSystem").setValue(sourceCRS);
param.parameter("GridGeometry").setValue(scaledGridGeometry);
param.parameter("InterpolationType")
.setValue(Interpolation.getInstance(Interpolation.INTERP_NEAREST));
final GridCoverage2D scaledGridCoverage = (GridCoverage2D) resampleFactory.doOperation(param,
hints);
return scaledGridCoverage;
}
/**
* <strong>Scaling</strong><br>
* Let user to scale down to the EXACT needed resolution. This step does not
* prevent from having loaded an overview of the original image based on the
* requested scale.
*
* @param destinationEnvelopeInSourceCRS
* @return GridCoverage2D
*/
public static GridCoverage2D scale(final GridCoverage2D coverage,
final GridGeometry2D scaledGridGeometry) {
final ParameterValueGroup param = (ParameterValueGroup) resampleParams.clone();
param.parameter("Source").setValue(coverage);
param.parameter("CoordinateReferenceSystem").setValue(coverage.getCoordinateReferenceSystem());
param.parameter("GridGeometry").setValue(scaledGridGeometry);
param.parameter("InterpolationType")
.setValue(Interpolation.getInstance(Interpolation.INTERP_NEAREST));
final GridCoverage2D scaledGridCoverage = (GridCoverage2D) resampleFactory.doOperation(param,
hints);
return scaledGridCoverage;
}
/**
* Scaling the input coverage using the provided parameters.
*
* @param scaleX
* @param scaleY
* @param xTrans
* @param yTrans
* @param interpolation
* @param be
* @param gc
* @return
*/
public static GridCoverage2D scale(final double scaleX, final double scaleY, float xTrans,
float yTrans, final Interpolation interpolation, final BorderExtender be,
final GridCoverage2D gc) {
final ParameterValueGroup param = (ParameterValueGroup) scaleParams.clone();
param.parameter("source").setValue(gc);
param.parameter("xScale").setValue(new Float(scaleX));
param.parameter("yScale").setValue(new Float(scaleY));
param.parameter("xTrans").setValue(new Float(xTrans));
param.parameter("yTrans").setValue(new Float(yTrans));
param.parameter("Interpolation").setValue(interpolation);
param.parameter("BorderExtender").setValue(be);
return (GridCoverage2D) scaleFactory.doOperation(param, hints);
}
/**
* Reprojecting the input coverage using the provided parameters.
*
* @param gc
* @param crs
* @param interpolation
* @return
*/
public static GridCoverage2D resample(final GridCoverage2D gc, CoordinateReferenceSystem crs,
final Interpolation interpolation) {
final ParameterValueGroup param = (ParameterValueGroup) resampleParams.clone();
param.parameter("source").setValue(gc);
param.parameter("CoordinateReferenceSystem").setValue(crs);
param.parameter("InterpolationType").setValue(interpolation);
return (GridCoverage2D) resampleFactory.doOperation(param, hints);
}
/**
* Subsampling the provided {@link GridCoverage2D} with the provided
* parameters.
*
* @param gc
* @param scaleXInt
* @param scaleYInt
* @param interpolation
* @param be
* @return
*/
public static GridCoverage2D filteredSubsample(final GridCoverage2D gc, int scaleXInt,
int scaleYInt, final Interpolation interpolation, final BorderExtender be) {
final GridCoverage2D preScaledGridCoverage;
if ((scaleXInt == 1) && (scaleYInt == 1)) {
preScaledGridCoverage = gc;
} else {
final ParameterValueGroup param = (ParameterValueGroup) filteredSubsampleParams.clone();
param.parameter("source").setValue(gc);
param.parameter("scaleX").setValue(new Integer(scaleXInt));
param.parameter("scaleY").setValue(new Integer(scaleYInt));
if (interpolation.equals(new InterpolationNearest())) {
param.parameter("qsFilterArray").setValue(new float[] { 1.0F });
} else {
param.parameter("qsFilterArray")
.setValue(new float[] { 0.5F, 1.0F / 3.0F, 0.0F, -1.0F / 12.0F });
}
param.parameter("Interpolation").setValue(interpolation);
param.parameter("BorderExtender").setValue(be);
preScaledGridCoverage = (GridCoverage2D) filteredSubsampleFactory.doOperation(param,
hints);
}
return preScaledGridCoverage;
}
/**
* <strong>Cropping</strong><br>
* The crop operation is responsible for selecting geographic subareas of
* the source coverage.
*
* @param coverage
* Coverage
* @param sourceEnvelope
* GeneralEnvelope
* @param sourceCRS
* CoordinateReferenceSystem
* @param destinationEnvelopeInSourceCRS
* GeneralEnvelope
* @return GridCoverage2D
* @throws WcsException
*/
public static GridCoverage2D crop(final Coverage coverage,
final GeneralEnvelope sourceEnvelope, final CoordinateReferenceSystem sourceCRS,
final GeneralEnvelope destinationEnvelopeInSourceCRS, final Boolean conserveEnvelope)
throws WcsException {
// ///////////////////////////////////////////////////////////////////
//
// CROP
//
//
// ///////////////////////////////////////////////////////////////////
final GridCoverage2D croppedGridCoverage;
// intersect the envelopes
final GeneralEnvelope intersectionEnvelope = new GeneralEnvelope(destinationEnvelopeInSourceCRS);
intersectionEnvelope.setCoordinateReferenceSystem(sourceCRS);
intersectionEnvelope.intersect((GeneralEnvelope) sourceEnvelope);
// dow we have something to show?
if (intersectionEnvelope.isEmpty()) {
throw new WcsException("The Intersection is null. Check the requested BBOX!");
}
if (!intersectionEnvelope.equals((GeneralEnvelope) sourceEnvelope)) {
// get the cropped grid geometry
// final GridGeometry2D cropGridGeometry = getCroppedGridGeometry(
// intersectionEnvelope, gridCoverage);
/* Operations.DEFAULT.crop(coverage, intersectionEnvelope) */
final ParameterValueGroup param = (ParameterValueGroup) cropParams.clone();
param.parameter("Source").setValue(coverage);
param.parameter("Envelope").setValue(intersectionEnvelope);
param.parameter("ConserveEnvelope").setValue(conserveEnvelope);
croppedGridCoverage = (GridCoverage2D) cropFactory.doOperation(param, hints);
} else {
croppedGridCoverage = (GridCoverage2D) coverage;
}
// prefetch to be faster afterwards.
// This step is important since at this stage we might be loading tiles
// from disk
croppedGridCoverage.prefetch(intersectionEnvelope.toRectangle2D());
return croppedGridCoverage;
}
/**
* <strong>Band Selecting</strong><br>
* Chooses <var>N</var>
* {@linkplain org.geotools.coverage.GridSampleDimension sample dimensions}
* from a grid coverage and copies their sample data to the destination grid
* coverage in the order specified. The {@code "SampleDimensions"} parameter
* specifies the source {@link org.geotools.coverage.GridSampleDimension}
* indices, and its size ({@code SampleDimensions.length}) determines the
* number of sample dimensions of the destination grid coverage. The
* destination coverage may have any number of sample dimensions, and a
* particular sample dimension of the source coverage may be repeated in the
* destination coverage by specifying it multiple times in the
* {@code "SampleDimensions"} parameter.
*
* @param params
* Set
* @param coverage
* GridCoverage
* @return Coverage
* @throws WcsException
*/
public static Coverage bandSelect(final Map params, final GridCoverage coverage)
throws WcsException {
// ///////////////////////////////////////////////////////////////////
//
// BAND SELECT
//
//
// ///////////////////////////////////////////////////////////////////
final int numDimensions = coverage.getNumSampleDimensions();
final Map dims = new HashMap();
final ArrayList selectedBands = new ArrayList();
for (int d = 0; d < numDimensions; d++) {
dims.put("band" + (d + 1), new Integer(d));
}
if ((params != null) && !params.isEmpty()) {
for (Iterator p = params.keySet().iterator(); p.hasNext();) {
final String param = (String) p.next();
if (param.equalsIgnoreCase("BAND")) {
try {
final String values = (String) params.get(param);
if (values.indexOf("/") > 0) {
final String[] minMaxRes = values.split("/");
final int min = (int) Math.round(Double.parseDouble(minMaxRes[0]));
final int max = (int) Math.round(Double.parseDouble(minMaxRes[1]));
final double res = ((minMaxRes.length > 2)
? Double.parseDouble(minMaxRes[2]) : 0.0);
for (int v = min; v <= max; v++) {
final String key = param.toLowerCase() + v;
if (dims.containsKey(key)) {
selectedBands.add(dims.get(key));
}
}
} else {
final String[] bands = values.split(",");
for (int v = 0; v < bands.length; v++) {
final String key = param.toLowerCase() + bands[v];
if (dims.containsKey(key)) {
selectedBands.add(dims.get(key));
}
}
if (selectedBands.size() == 0) {
throw new Exception("WRONG PARAM VALUES.");
}
}
} catch (Exception e) {
throw new WcsException("Band parameters incorrectly specified: "
+ e.getLocalizedMessage());
}
}
}
}
final int length = selectedBands.size();
final int[] bands = new int[length];
for (int b = 0; b < length; b++) {
bands[b] = ((Integer) selectedBands.get(b)).intValue();
}
return bandSelect(coverage, bands);
}
public static Coverage bandSelect(final GridCoverage coverage, final int[] bands) {
Coverage bandSelectedCoverage;
if ((bands != null) && (bands.length > 0)) {
/* Operations.DEFAULT.selectSampleDimension(coverage, bands) */
final ParameterValueGroup param = (ParameterValueGroup) bandSelectParams.clone();
param.parameter("Source").setValue(coverage);
param.parameter("SampleDimensions").setValue(bands);
// param.parameter("VisibleSampleDimension").setValue(bands);
bandSelectedCoverage = bandSelectFactory.doOperation(param, hints);
} else {
bandSelectedCoverage = coverage;
}
return bandSelectedCoverage;
}
}