diff --git a/src/community/wcs2_0/src/main/java/applicationContext.xml b/src/community/wcs2_0/src/main/java/applicationContext.xml index 94683109b91..5cc397dc9c9 100644 --- a/src/community/wcs2_0/src/main/java/applicationContext.xml +++ b/src/community/wcs2_0/src/main/java/applicationContext.xml @@ -114,7 +114,7 @@ - + diff --git a/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/DefaultWebCoverageService20.java b/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/DefaultWebCoverageService20.java index df53b9f89cd..2ff534b02bc 100644 --- a/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/DefaultWebCoverageService20.java +++ b/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/DefaultWebCoverageService20.java @@ -18,7 +18,7 @@ import org.geoserver.wcs.responses.CoverageResponseDelegateFinder; import org.geoserver.wcs2_0.exception.WCS20Exception; import org.geoserver.wcs2_0.response.WCS20DescribeCoverageTransformer; -import org.geoserver.wcs2_0.util.EnvelopeDimensionsMapper; +import org.geoserver.wcs2_0.util.EnvelopeAxesLabelsMapper; import org.geotools.util.logging.Logging; import org.geotools.xml.transform.TransformerBase; import org.opengis.coverage.grid.GridCoverage; @@ -39,13 +39,13 @@ public class DefaultWebCoverageService20 implements WebCoverageService20 { private CoverageResponseDelegateFinder responseFactory; /** Utility class to map envelope dimension*/ - private EnvelopeDimensionsMapper envelopeDimensionsMapper; + private EnvelopeAxesLabelsMapper envelopeAxesMapper; - public DefaultWebCoverageService20(GeoServer geoServer, CoverageResponseDelegateFinder responseFactory, EnvelopeDimensionsMapper envelopeDimensionsMapper) { + public DefaultWebCoverageService20(GeoServer geoServer, CoverageResponseDelegateFinder responseFactory, EnvelopeAxesLabelsMapper envelopeDimensionsMapper) { this.geoServer = geoServer; this.catalog = geoServer.getCatalog(); this.responseFactory = responseFactory; - this.envelopeDimensionsMapper=envelopeDimensionsMapper; + this.envelopeAxesMapper=envelopeDimensionsMapper; } @Override @@ -67,12 +67,12 @@ public WCS20DescribeCoverageTransformer describeCoverage(DescribeCoverageType re checkVersion(request.getVersion()); if( request.getCoverageId() == null || request.getCoverageId().isEmpty() ) { - throw new OWS20Exception("Required parameter coverageId missing", WCS20Exception.WCSExceptionCode.EmptyCoverageIdList, "coverageId"); + throw new OWS20Exception("Required parameter coverageId missing", WCS20Exception.WCS20ExceptionCode.EmptyCoverageIdList, "coverageId"); } WCSInfo wcs = getServiceInfo(); - WCS20DescribeCoverageTransformer describeTransformer = new WCS20DescribeCoverageTransformer(wcs, catalog, responseFactory,envelopeDimensionsMapper); + WCS20DescribeCoverageTransformer describeTransformer = new WCS20DescribeCoverageTransformer(wcs, catalog, responseFactory,envelopeAxesMapper); describeTransformer.setEncoding(Charset.forName(wcs.getGeoServer().getSettings().getCharset())); return describeTransformer; } @@ -83,10 +83,10 @@ public GridCoverage getCoverage(GetCoverageType request) { checkVersion(request.getVersion()); if( request.getCoverageId() == null || "".equals(request.getCoverageId()) ) { - throw new OWS20Exception("Required parameter coverageId missing", WCS20Exception.WCSExceptionCode.EmptyCoverageIdList, "coverageId"); + throw new OWS20Exception("Required parameter coverageId missing", WCS20Exception.WCS20ExceptionCode.EmptyCoverageIdList, "coverageId"); } - return new GetCoverage(getServiceInfo(), catalog,envelopeDimensionsMapper).run(request); + return new GetCoverage(getServiceInfo(), catalog,envelopeAxesMapper).run(request); } private void checkVersion(String version) { diff --git a/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/GetCoverage.java b/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/GetCoverage.java index 9b2a0ce8b9d..b44d7344a6f 100644 --- a/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/GetCoverage.java +++ b/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/GetCoverage.java @@ -3,6 +3,10 @@ import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import javax.media.jai.Interpolation; import javax.media.jai.JAI; @@ -18,30 +22,54 @@ import net.opengis.wcs20.ExtensionItemType; import net.opengis.wcs20.ExtensionType; import net.opengis.wcs20.GetCoverageType; +import net.opengis.wcs20.InterpolationAxesType; +import net.opengis.wcs20.InterpolationAxisType; +import net.opengis.wcs20.InterpolationMethodType; +import net.opengis.wcs20.InterpolationType; +import net.opengis.wcs20.RangeIntervalType; +import net.opengis.wcs20.RangeItemType; +import net.opengis.wcs20.RangeSubsetType; +import net.opengis.wcs20.ScaleAxisByFactorType; +import net.opengis.wcs20.ScaleAxisType; +import net.opengis.wcs20.ScaleByFactorType; +import net.opengis.wcs20.ScaleToExtentType; +import net.opengis.wcs20.ScaleToSizeType; +import net.opengis.wcs20.ScalingType; +import net.opengis.wcs20.TargetAxisExtentType; +import net.opengis.wcs20.TargetAxisSizeType; import org.eclipse.emf.common.util.EList; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CoverageInfo; import org.geoserver.catalog.LayerInfo; +import org.geoserver.platform.OWS20Exception; import org.geoserver.wcs.CoverageCleanerCallback; import org.geoserver.wcs.WCSInfo; import org.geoserver.wcs2_0.exception.WCS20Exception; -import org.geoserver.wcs2_0.util.EnvelopeDimensionsMapper; +import org.geoserver.wcs2_0.exception.WCS20Exception.WCS20ExceptionCode; +import org.geoserver.wcs2_0.util.EnvelopeAxesLabelsMapper; import org.geoserver.wcs2_0.util.NCNameResourceCodec; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridEnvelope2D; +import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.coverage.processing.CoverageProcessor; import org.geotools.coverage.processing.Operations; import org.geotools.factory.Hints; import org.geotools.geometry.GeneralEnvelope; +import org.geotools.referencing.CRS; +import org.geotools.resources.coverage.CoverageUtilities; +import org.geotools.util.Utilities; import org.jaitools.imageutils.ImageLayout2; +import org.opengis.coverage.Coverage; import org.opengis.coverage.grid.GridCoverage; import org.opengis.coverage.grid.GridCoverageReader; +import org.opengis.coverage.grid.GridGeometry; import org.opengis.coverage.processing.Operation; +import org.opengis.geometry.Envelope; import org.opengis.parameter.ParameterValueGroup; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.MathTransform; import org.vfny.geoserver.util.WCSUtils; -import org.vfny.geoserver.wcs.WcsException; -import org.vfny.geoserver.wcs.WcsException.WcsExceptionCode; /** * Implementation of the WCS 2.0 GetCoverage request @@ -51,11 +79,85 @@ */ public class GetCoverage { + public enum InterpolationPolicy{ + linear("http://www.opengis.net/def/interpolation/OGC/1/linear") { + @Override + public Interpolation getInterpolation() { + return Interpolation.getInstance(Interpolation.INTERP_BILINEAR); + } + },nearestneighbor("http://www.opengis.net/def/interpolation/OGC/1/nearest-neighbor") { + @Override + public Interpolation getInterpolation() { + return Interpolation.getInstance(Interpolation.INTERP_NEAREST); + } + },quadratic("http://www.opengis.net/def/interpolation/OGC/1/quadratic") { + @Override + public Interpolation getInterpolation() { + throw new WCS20Exception( + "Interpolation not supported", + WCS20Exception.WCS20ExceptionCode.InterpolationMethodNotSupported, + quadratic.toString()); + } + },cubic("http://www.opengis.net/def/interpolation/OGC/1/cubic") { + @Override + public Interpolation getInterpolation() { + return Interpolation.getInstance(Interpolation.INTERP_BICUBIC_2); + } + },lostarea("http://www.opengis.net/def/interpolation/OGC/1/lost-area") { + @Override + public Interpolation getInterpolation() { + throw new WCS20Exception( + "Interpolation not supported", + WCS20Exception.WCS20ExceptionCode.InterpolationMethodNotSupported, + lostarea.toString()); + } + },barycentric("http://www.opengis.net/def/interpolation/OGC/1/barycentric") { + @Override + public Interpolation getInterpolation() { + throw new WCS20Exception( + "Interpolation not supported", + WCS20Exception.WCS20ExceptionCode.InterpolationMethodNotSupported, + barycentric.toString()); + } + }; + + private InterpolationPolicy(String representation) { + this.strVal = representation; + } + + private final String strVal; + + abstract public Interpolation getInterpolation(); + + static InterpolationPolicy getPolicy(InterpolationMethodType interpolationMethodType){ + Utilities.ensureNonNull("interpolationMethodType", interpolationMethodType); + final String interpolationMethod=interpolationMethodType.getInterpolationMethod(); + return getPolicy(interpolationMethod); + } + + static InterpolationPolicy getPolicy(String interpolationMethod){ + Utilities.ensureNonNull("interpolationMethod", interpolationMethod); + final InterpolationPolicy[] values = InterpolationPolicy.values(); + for(InterpolationPolicy policy:values){ + if(policy.strVal.equals(interpolationMethod)){ + return policy; + } + } + + //method not found + throw new WCS20Exception("Interpolation method not supported",WCS20ExceptionCode.InterpolationMethodNotSupported,interpolationMethod); + } + + static InterpolationPolicy getDefaultPolicy(){ + return nearestneighbor; + } + } + private enum ScalingPolicy{ DoNothing{ @Override - public GridCoverage2D scale(GridCoverage2D sourceGC, ExtensionItemType extensionItem) { + public GridCoverage2D scale(GridCoverage2D sourceGC, ScalingType scaling) { return sourceGC; } @@ -63,29 +165,26 @@ public GridCoverage2D scale(GridCoverage2D sourceGC, ExtensionItemType extension ScaleByFactor{ @Override - public GridCoverage2D scale(GridCoverage2D sourceGC, ExtensionItemType extensionItem) { + public GridCoverage2D scale(GridCoverage2D sourceGC, ScalingType scaling) { // get scale factor - final String scaleFactorS=extensionItem.getSimpleContent(); - float scaleFactor=Float.NaN; - try{ - scaleFactor=Float.parseFloat(scaleFactorS); - if(scaleFactor<0){ - throw new WCS20Exception("Invalid scale factor", WCS20Exception.WCSExceptionCode.InvalidScaleFactor, String.valueOf(scaleFactor)); - } - } catch (Exception e) { - throw new WCS20Exception("Invalid scale factor", WCS20Exception.WCSExceptionCode.InvalidScaleFactor, scaleFactorS); + final ScaleByFactorType scaleByFactorType = scaling.getScaleByFactor(); + double scaleFactor=scaleByFactorType.getScaleFactor(); + + // checks + if(scaleFactor<0){ + throw new WCS20Exception("Invalid scale factor", WCS20Exception.WCS20ExceptionCode.InvalidScaleFactor, String.valueOf(scaleFactor)); } // return coverage unchanged if we don't scale if(scaleFactor==1){ return sourceGC; } + + // checks return (GridCoverage2D)Operations.DEFAULT.scale(sourceGC, scaleFactor, scaleFactor, 0, 0, Interpolation.getInstance(Interpolation.INTERP_NEAREST)); } }, - - ScaleToSize{ /** @@ -94,27 +193,30 @@ public GridCoverage2D scale(GridCoverage2D sourceGC, ExtensionItemType extension * */ @Override - public GridCoverage2D scale(GridCoverage2D sourceGC, ExtensionItemType extensionItem) { - // get scale factor - final String sizeXS=extensionItem.getSimpleContent(); - final String sizeYS=extensionItem.getSimpleContent(); - int sizeX=Integer.MIN_VALUE; - try{ - sizeX=Integer.parseInt(sizeXS); - if(sizeX<=0){ - throw new WCS20Exception("Invalid target size", WCS20Exception.WCSExceptionCode.InvalidScaleFactor, sizeXS); + public GridCoverage2D scale(GridCoverage2D sourceGC, ScalingType scaling) { + // get scale size + final ScaleToSizeType scaleType = scaling.getScaleToSize(); + final EList targetAxisSizeElements = scaleType.getTargetAxisSize(); + + TargetAxisSizeType xSize=null,ySize=null; + for(TargetAxisSizeType axisSizeType:targetAxisSizeElements){ + final String axisName=axisSizeType.getAxis(); + if(axisName.equals("http://www.opengis.net/def/axis/OGC/1/i")){ + xSize=axisSizeType; + } else if(axisName.equals("http://www.opengis.net/def/axis/OGC/1/j")){ + ySize=axisSizeType; + } else { + // TODO remove when supporting TIME and ELEVATION + throw new WCS20Exception("Scale Axis Undefined", WCS20Exception.WCS20ExceptionCode.ScaleAxisUndefined, axisName); } - } catch (Exception e) { - throw new WCS20Exception("Invalid target size", WCS20Exception.WCSExceptionCode.InvalidScaleFactor, sizeXS); } - int sizeY=Integer.MIN_VALUE; - try{ - sizeY=Integer.parseInt(sizeYS); - if(sizeX<=0){ - throw new WCS20Exception("Invalid target size", WCS20Exception.WCSExceptionCode.InvalidScaleFactor, sizeYS); - } - } catch (Exception e) { - throw new WCS20Exception("Invalid target size", WCS20Exception.WCSExceptionCode.InvalidScaleFactor, sizeYS); + final int sizeX=(int) xSize.getTargetSize();// TODO should this be int? + if(sizeX<=0){ + throw new WCS20Exception("Invalid target size", WCS20Exception.WCS20ExceptionCode.InvalidScaleFactor, Integer.toString(sizeX)); + } + final int sizeY=(int) ySize.getTargetSize();// TODO should this be int? + if(sizeY<=0){ + throw new WCS20Exception("Invalid target size", WCS20Exception.WCS20ExceptionCode.InvalidScaleFactor, Integer.toString(sizeY)); } // unscale @@ -125,54 +227,64 @@ public GridCoverage2D scale(GridCoverage2D sourceGC, ExtensionItemType extension // create final warp final Warp warp= new WarpAffine(AffineTransform.getScaleInstance(sourceGE.width/sizeX, sourceGE.height/sizeY));// TODO check + // impose final final ImageLayout2 layout = new ImageLayout2( sourceGE.x, sourceGE.y, sizeX, sizeY); - final Hints hints= new Hints(JAI.KEY_IMAGE_LAYOUT, layout); - + final Hints hints= new Hints(JAI.KEY_IMAGE_LAYOUT, layout); final Operation operation = CoverageProcessor.getInstance().getOperation("Warp"); final ParameterValueGroup parameters = operation.getParameters(); parameters.parameter("Source").setValue(sourceGC); parameters.parameter("warp").setValue(warp); parameters.parameter("interpolation").setValue(Interpolation.getInstance(Interpolation.INTERP_NEAREST)); -// setParameterValue(parameters, "backgroundValues", argumentValue3);// TODO + parameters.parameter( "backgroundValues").setValue(CoverageUtilities.getBackgroundValues(sourceGC));// TODO check and improve return (GridCoverage2D) CoverageProcessor.getInstance().doOperation(parameters,hints); } + }, ScaleToExtent{ @Override - public GridCoverage2D scale(GridCoverage2D sourceGC, ExtensionItemType extensionItem) { - // get scale factor - final String sizeXS=extensionItem.getSimpleContent(); - final String sizeYS=extensionItem.getSimpleContent(); - int sizeX=Integer.MIN_VALUE; - try{ - sizeX=Integer.parseInt(sizeXS); - if(sizeX<=0){ - throw new WCS20Exception("Invalid target size", WCS20Exception.WCSExceptionCode.InvalidScaleFactor, sizeXS); + public GridCoverage2D scale(GridCoverage2D sourceGC, ScalingType scaling) { + // parse area + final ScaleToExtentType scaleType = scaling.getScaleToExtent(); + final EList targetAxisExtentElements = scaleType.getTargetAxisExtent(); + + TargetAxisExtentType xExtent=null,yExtent=null; + for(TargetAxisExtentType axisExtentType:targetAxisExtentElements){ + final String axisName=axisExtentType.getAxis(); + if(axisName.equals("http://www.opengis.net/def/axis/OGC/1/i")){ + xExtent=axisExtentType; + } else if(axisName.equals("http://www.opengis.net/def/axis/OGC/1/j")){ + yExtent=axisExtentType; + } else { + // TODO remove when supporting TIME and ELEVATION + throw new WCS20Exception("Scale Axis Undefined", WCS20Exception.WCS20ExceptionCode.ScaleAxisUndefined, axisName); } - } catch (Exception e) { - throw new WCS20Exception("Invalid target size", WCS20Exception.WCSExceptionCode.InvalidScaleFactor, sizeXS); } - int sizeY=Integer.MIN_VALUE; - try{ - sizeY=Integer.parseInt(sizeYS); - if(sizeX<=0){ - throw new WCS20Exception("Invalid target size", WCS20Exception.WCSExceptionCode.InvalidScaleFactor, sizeYS); - } - } catch (Exception e) { - throw new WCS20Exception("Invalid target size", WCS20Exception.WCSExceptionCode.InvalidScaleFactor, sizeYS); + if(xExtent==null){ + throw new WCS20Exception("Missing extent along i", WCS20Exception.WCS20ExceptionCode.InvalidScaleFactor, ""); } + if(yExtent==null){ + throw new WCS20Exception("Missing extent along j", WCS20Exception.WCS20ExceptionCode.InvalidScaleFactor, ""); + } - + final int minx=(int) targetAxisExtentElements.get(0).getLow();// TODO should this be int? + final int maxx=(int) targetAxisExtentElements.get(0).getHigh(); + final int miny=(int) targetAxisExtentElements.get(1).getLow(); + final int maxy=(int) targetAxisExtentElements.get(1).getHigh(); + + // check on source geometry final GridEnvelope2D sourceGE=sourceGC.getGridGeometry().getGridRange2D(); - final int minx=0; - final int miny=0; - final int maxx=0; - final int maxy=0; + + if(minx>maxx){ + throw new WCS20Exception("Invalid Extent for dimension:"+targetAxisExtentElements.get(0).getAxis() , WCS20Exception.WCS20ExceptionCode.InvalidExtent, String.valueOf(maxx)); + } + if(miny>maxy){ + throw new WCS20Exception("Invalid Extent for dimension:"+targetAxisExtentElements.get(1).getAxis() , WCS20Exception.WCS20ExceptionCode.InvalidExtent, String.valueOf(maxy)); + } final Rectangle destinationRectangle= new Rectangle(minx, miny,maxx-minx+1, maxy-miny+1); // UNSCALE if(destinationRectangle.equals(sourceGE)){ @@ -180,7 +292,10 @@ public GridCoverage2D scale(GridCoverage2D sourceGC, ExtensionItemType extension } // create final warp - final Warp warp= new WarpAffine(AffineTransform.getScaleInstance(sourceGE.width/sizeX, sourceGE.height/sizeY)); // TODO check + final Warp warp= new WarpAffine(AffineTransform.getScaleInstance( + sourceGE.width/destinationRectangle.width, + sourceGE.height/destinationRectangle.height)); // TODO check + // impose size final ImageLayout2 layout = new ImageLayout2( destinationRectangle.x, destinationRectangle.y, @@ -193,7 +308,7 @@ public GridCoverage2D scale(GridCoverage2D sourceGC, ExtensionItemType extension parameters.parameter("Source").setValue(sourceGC); parameters.parameter("warp").setValue(warp); parameters.parameter("interpolation").setValue(Interpolation.getInstance(Interpolation.INTERP_NEAREST)); -// setParameterValue(parameters, "backgroundValues", argumentValue3);// TODO + parameters.parameter( "backgroundValues").setValue(CoverageUtilities.getBackgroundValues(sourceGC));// TODO check and improve return (GridCoverage2D) CoverageProcessor.getInstance().doOperation(parameters,hints); } @@ -201,30 +316,40 @@ public GridCoverage2D scale(GridCoverage2D sourceGC, ExtensionItemType extension ScaleAxesByFactor{ @Override - public GridCoverage2D scale(GridCoverage2D sourceGC, ExtensionItemType extensionItem) { + public GridCoverage2D scale(GridCoverage2D sourceGC, ScalingType scaling) { // TODO dimension management // get scale factor - final String scaleFactorSX=extensionItem.getSimpleContent(); - final String scaleFactorSY=extensionItem.getSimpleContent(); - float scaleFactorX=Float.NaN; - try{ - scaleFactorX=Float.parseFloat(scaleFactorSX); - if(scaleFactorX<0){ - throw new WCS20Exception("Invalid scale factor", WCS20Exception.WCSExceptionCode.InvalidScaleFactor, scaleFactorSX); + final ScaleAxisByFactorType scaleType = scaling.getScaleAxesByFactor(); + final EList targetAxisScaleElements = scaleType.getScaleAxis(); + + ScaleAxisType xScale=null,yScale=null; + for(ScaleAxisType scaleAxisType:targetAxisScaleElements){ + final String axisName=scaleAxisType.getAxis(); + if(axisName.equals("http://www.opengis.net/def/axis/OGC/1/i")){ + xScale=scaleAxisType; + } else if(axisName.equals("http://www.opengis.net/def/axis/OGC/1/j")){ + yScale=scaleAxisType; + } else { + // TODO remove when supporting TIME and ELEVATION + throw new WCS20Exception("Scale Axis Undefined", WCS20Exception.WCS20ExceptionCode.ScaleAxisUndefined, axisName); } - } catch (Exception e) { - throw new WCS20Exception("Invalid scale factor", WCS20Exception.WCSExceptionCode.InvalidScaleFactor, scaleFactorSX); } - float scaleFactorY=Float.NaN; - try{ - scaleFactorY=Float.parseFloat(scaleFactorSX); - if(scaleFactorY<0){ - throw new WCS20Exception("Invalid scale factor", WCS20Exception.WCSExceptionCode.InvalidScaleFactor, scaleFactorSY); - } - } catch (Exception e) { - throw new WCS20Exception("Invalid scale factor", WCS20Exception.WCSExceptionCode.InvalidScaleFactor, scaleFactorSY); - } + if(xScale==null){ + throw new WCS20Exception("Missing scale factor along i", WCS20Exception.WCS20ExceptionCode.InvalidScaleFactor, ""); + } + if(yScale==null){ + throw new WCS20Exception("Missing scale factor along j", WCS20Exception.WCS20ExceptionCode.InvalidScaleFactor, ""); + } + + final double scaleFactorX= xScale.getScaleFactor();// TODO should this be int? + if(scaleFactorX<0){ + throw new WCS20Exception("Invalid scale factor", WCS20Exception.WCS20ExceptionCode.InvalidScaleFactor, Double.toString(scaleFactorX)); + } + final double scaleFactorY= yScale.getScaleFactor();// TODO should this be int? + if(scaleFactorY<0){ + throw new WCS20Exception("Invalid scale factor", WCS20Exception.WCS20ExceptionCode.InvalidScaleFactor, Double.toString(scaleFactorY)); + } // unscale if(scaleFactorX==1.0&& scaleFactorY==1.0){ @@ -236,13 +361,33 @@ public GridCoverage2D scale(GridCoverage2D sourceGC, ExtensionItemType extension }; /** - * @param extensionItem + * @param scaling * @param sourceGG * @param width * @param origin * @param returnValue */ - abstract public GridCoverage2D scale(GridCoverage2D sourceGC, ExtensionItemType extensionItem) ; + abstract public GridCoverage2D scale(GridCoverage2D sourceGC, ScalingType scaling) ; + + + public static ScalingPolicy getPolicy(ScalingType scaling) { + if(scaling!=null){ + if (scaling.getScaleAxesByFactor() != null) { + return ScaleAxesByFactor; + } + if (scaling.getScaleByFactor() != null) { + return ScaleByFactor; + } + if (scaling.getScaleToExtent() != null) { + return ScaleToExtent; + } + if (scaling.getScaleToSize() != null) { + return ScalingPolicy.ScaleToSize; + } + + } + return DoNothing; + } }; @@ -250,9 +395,9 @@ public GridCoverage2D scale(GridCoverage2D sourceGC, ExtensionItemType extension private Catalog catalog; /** Utility class to map envelope dimension*/ - private EnvelopeDimensionsMapper envelopeDimensionsMapper; + private EnvelopeAxesLabelsMapper envelopeDimensionsMapper; - public GetCoverage(WCSInfo serviceInfo, Catalog catalog, EnvelopeDimensionsMapper envelopeDimensionsMapper) { + public GetCoverage(WCSInfo serviceInfo, Catalog catalog, EnvelopeAxesLabelsMapper envelopeDimensionsMapper) { this.wcs = serviceInfo; this.catalog = catalog; this.envelopeDimensionsMapper=envelopeDimensionsMapper; @@ -263,7 +408,7 @@ public GridCoverage run(GetCoverageType request) { LayerInfo linfo = NCNameResourceCodec.getCoverage(catalog, request.getCoverageId()); if(linfo == null) { throw new WCS20Exception("Could not locate coverage " + request.getCoverageId(), - WCS20Exception.WCSExceptionCode.NoSuchCoverage, "coverageId"); + WCS20Exception.WCS20ExceptionCode.NoSuchCoverage, "coverageId"); } // TODO: handle trimming and slicing @@ -277,6 +422,8 @@ public GridCoverage run(GetCoverageType request) { // use this to check if we can use overviews or not boolean subsample = wcs.isSubsamplingEnabled(); + Map extensions = extractExtensions(coverage,request); + // TODO: setup the params to force the usage of imageread and to make it use // the right overview and so on // TODO here we should really try to subset before reading with a grid geometry @@ -285,22 +432,29 @@ public GridCoverage run(GetCoverageType request) { // TODO: handle crop, scale, reproject and so on + // get CRS extension values + final CoordinateReferenceSystem subsettingCRS=extractSubsettingCRS(coverage,extensions); + final CoordinateReferenceSystem outputCRS=extractOutputCRS(coverage,extensions); + + // handle interpolation + final Map axesInterpolations=extractInterpolation(coverage,extensions); + // handle range subsetting - coverage=handleRangeSubsettingExtension(coverage,request); + coverage=handleRangeSubsettingExtension(coverage,extensions); + + // subsetting, is not really an extension + coverage=handleSubsettingExtension(coverage,request,subsettingCRS); - // scaling subsetting - coverage=handleSubsettingExtension(coverage,request); -// - // scaling subsetting - coverage=handleScaling(coverage,request); + // scaling + coverage=handleScaling(coverage,extensions,axesInterpolations); - // scaling subsetting - coverage=handleReprojection(coverage,request); + // reprojection + coverage=handleReprojection(coverage,outputCRS,axesInterpolations); // reproject } catch(IOException e) { - throw new WcsException("Failed to read the coverage " + request.getCoverageId(), e); + throw new WCS20Exception("Failed to read the coverage " + request.getCoverageId(), e); } finally { // make sure the coverage will get cleaned at the end of the processing if(coverage != null) { @@ -313,29 +467,72 @@ public GridCoverage run(GetCoverageType request) { /** * @param coverage - * @param request + * @param extensions * @return */ - private GridCoverage2D handleReprojection(GridCoverage2D coverage, GetCoverageType request) { - // look for scaling extension - final ExtensionType extension = request.getExtension(); - if(extension==null){ - // NO SCALING - return coverage; + private CoordinateReferenceSystem extractOutputCRS(GridCoverage2D coverage, + Map extensions) { + return extractCRS(coverage,extensions,false); + } + + /** + * @param coverage + * @param extensions + * @return + */ + private CoordinateReferenceSystem extractSubsettingCRS(GridCoverage2D coverage,Map extensions) { + return extractCRS(coverage,extensions,true); + } + + /** + * @param coverage + * @param extensions + * @param b + * @return + */ + private CoordinateReferenceSystem extractCRS(GridCoverage2D coverage, Map extensions, + boolean subsettingCRS) { + + // look for subsettingCRS Extension extension + if(extensions==null||extensions.size()==0||!extensions.containsKey(subsettingCRS ? "subsettingCrs" : "outputCrs")){ + // NO INTERPOLATION + return coverage.getCoordinateReferenceSystem2D(); } - // look for a scaling extension - final EList extensions = extension.getContents(); - for(ExtensionItemType extensionItem:extensions){ - try{ - final ScalingPolicy scaling= ScalingPolicy.valueOf(extensionItem.getName());// TODO extract scaling behavior - return scaling.scale(coverage, extensionItem); + // look for an interpolation extension + final ExtensionItemType extensionItem=extensions.get(subsettingCRS ? "subsettingCrs" : "outputCrs"); + if (extensionItem.getName().equals(subsettingCRS ? "subsettingCrs" : "outputCrs")) { + // get URI + String crsName = extensionItem.getSimpleContent(); + + // checks + if (crsName == null) { + throw new WCS20Exception(subsettingCRS ? "Subsetting" : "Output" + " CRS was null", + WCS20ExceptionCode.NotACrs, "null"); + } + + // instantiate + final int lastSlash = crsName.lastIndexOf("/"); + // error no valid URI + // TODO improve checs + if (lastSlash < 0) { + throw new WCS20Exception("Invalid " + (subsettingCRS ? "subsetting" : "output") + + " CRS", WCS20Exception.WCS20ExceptionCode.NotACrs, crsName); + } + crsName = crsName.substring(lastSlash + 1, crsName.length()); + // instantiate + try { + return CRS.decode("EPSG:" + crsName, false); // notice the usage of boolean param } catch (Exception e) { - // eat me + final WCS20Exception exception = new WCS20Exception("Invalid " + + (subsettingCRS ? "subsetting" : "output") + " CRS", + WCS20Exception.WCS20ExceptionCode.NotACrs, crsName); + exception.initCause(e); + throw exception; } + } - // no scaling - return coverage; + return coverage.getCoordinateReferenceSystem2D(); } /** @@ -343,20 +540,190 @@ private GridCoverage2D handleReprojection(GridCoverage2D coverage, GetCoverageTy * @param request * @return */ - private GridCoverage2D handleRangeSubsettingExtension(GridCoverage2D coverage, - GetCoverageType request) { + private Map extractExtensions(GridCoverage2D coverage, GetCoverageType request) { + // look for subsettingCRS Extension extension + final ExtensionType extension = request.getExtension(); + + // look for the various extensions + final Map parsedExtensions=new HashMap(); + // no extensions? + if(extension!=null){ + final EList extensions = extension.getContents(); + for (final ExtensionItemType extensionItem : extensions) { + final String extensionName = extensionItem.getName(); + if (extensionName == null || extensionName.length() <= 0) { + throw new WCS20Exception("Null extension"); + } + if (extensionName.equals("subsettingCrs")) { + parsedExtensions.put("subsettingCrs", extensionItem); + } else if (extensionName.equals("outputCrs")) { + parsedExtensions.put("outputCrs", extensionItem); + } else if (extensionName.equals("Scaling")) { + parsedExtensions.put("Scaling", extensionItem); + } else if (extensionName.equals("Interpolation")) { + parsedExtensions.put("Interpolation", extensionItem); + } else if (extensionName.equals("rangeSubset")) { + parsedExtensions.put("rangeSubset", extensionItem); + } else if (extensionName.equals("rangeSubset")) { + parsedExtensions.put("rangeSubset", extensionItem); + } + } + } + return parsedExtensions; + } + + /** + * @param coverage + * @param extensions2 + * @return + */ + private Map extractInterpolation(GridCoverage2D coverage, Map extensions) { + // preparation + final Map returnValue= new HashMap(); + final Envelope envelope= coverage.getEnvelope(); + final List axesNames = envelopeDimensionsMapper.getAxesNames(envelope, true); + for(String axisName:axesNames){ + returnValue.put(axisName, InterpolationPolicy.getDefaultPolicy());// use defaults if no specified + } + + // look for scaling extension + if(extensions==null||extensions.size()==0||!extensions.containsKey("Interpolation")){ + // NO INTERPOLATION + return returnValue; + } + + // look for an interpolation extension + final ExtensionItemType extensionItem=extensions.get("Interpolation"); + // get interpolationType + InterpolationType interpolationType = (InterpolationType) extensionItem + .getObjectContent(); + // which type + if (interpolationType.getInterpolationMethod() != null) { + InterpolationMethodType method = interpolationType.getInterpolationMethod(); + InterpolationPolicy policy = InterpolationPolicy.getPolicy(method); + for (String axisName : axesNames) { + returnValue.put(axisName, policy); + } + + } else if (interpolationType.getInterpolationAxes() != null) { + // make sure we don't set things twice + final List foundAxes=new ArrayList(); + + final InterpolationAxesType axes = interpolationType.getInterpolationAxes(); + for (InterpolationAxisType axisInterpolation : axes.getInterpolationAxis()) { + + // parse interpolation + final String method = axisInterpolation.getInterpolationMethod(); + final InterpolationPolicy policy = InterpolationPolicy.getPolicy(method); + + // parse axis + final String axis = axisInterpolation.getAxis(); + // get label from axis + // TODO synonyms reduction + int index = axis.lastIndexOf("/"); + final String axisLabel = (index >= 0 ? axis.substring(index+1, axis.length()) + : axis); + + // did we already set this interpolation? + if(foundAxes.contains(axisLabel)){ + throw new WCS20Exception("Duplicated axis",WCS20Exception.WCS20ExceptionCode.InvalidAxisLabel,axisLabel); + } + foundAxes.add(axisLabel); + + // do we have this axis? + if(!returnValue.containsKey(axisLabel)){ + throw new WCS20Exception("Invalid axes URI",WCS20Exception.WCS20ExceptionCode.NoSuchAxis,axisLabel); + } + returnValue.put(axisLabel, policy); + } + } + + // final checks, we dont' supported different interpolations on Long and Lat + InterpolationPolicy lat=null,lon=null; + if(returnValue.containsKey("Long")){ + lon=returnValue.get("Long"); + } + if(returnValue.containsKey("Lat")){ + lat=returnValue.get("Lat"); + } + if(lat!=lon){ + throw new WCS20Exception("We don't support different interpolations on Lat,Lon", WCS20Exception.WCS20ExceptionCode.InterpolationMethodNotSupported,""); + } + returnValue.get("Lat"); + return returnValue; + } + + /** + * @param coverage + * @param outputCRS + * @param axesInterpolations + * @return + */ + private GridCoverage2D handleReprojection(GridCoverage2D coverage, CoordinateReferenceSystem outputCRS, Map axesInterpolations) { + + // check the two crs tosee if we really need to do anything + if(CRS.equalsIgnoreMetadata(coverage.getCoordinateReferenceSystem2D(), outputCRS)){ + return coverage; + } + // resample + return (GridCoverage2D) Operations.DEFAULT.resample(coverage, outputCRS); + } + + /** + * @param coverage + * @param extensions + * @return + */ + private GridCoverage2D handleRangeSubsettingExtension( + GridCoverage2D coverage, + Map extensions) { + // preparation + final List returnValue=new ArrayList(); + + // look for scaling extension + if(extensions==null||extensions.size()==0||!extensions.containsKey("rangeSubset")){ + // NO INTERPOLATION + return coverage; + } + + // look for an interpolation extension + final ExtensionItemType extensionItem=extensions.get("rangeSubset"); + final RangeSubsetType range = (RangeSubsetType) extensionItem.getObjectContent(); + for(RangeItemType rangeItem: range.getRangeItems()){ + // there you go the range item + + // single element + final String rangeComponent=rangeItem.getRangeComponent(); + + // range? + if(rangeComponent==null){ + final RangeIntervalType rangeInterval = rangeItem.getRangeInterval(); + final String startRangeComponent=rangeInterval.getStartComponent(); + final String endRangeComponent=rangeInterval.getEndComponent(); + + returnValue.add(startRangeComponent); + returnValue.add(endRangeComponent ); + } else { + returnValue.add(rangeComponent); + } + } + return coverage; } /** * @param coverage * @param request + * @param subsettingCRS * @return */ - private GridCoverage2D handleSubsettingExtension(GridCoverage2D coverage, GetCoverageType request) { + private GridCoverage2D handleSubsettingExtension( + GridCoverage2D coverage, + GetCoverageType request, + CoordinateReferenceSystem subsettingCRS) { // extract subsetting - final GeneralEnvelope subset=extractSubsettingEnvelope(coverage,request); + final GeneralEnvelope subset=extractSubsettingEnvelope(coverage,request,subsettingCRS); if(subset!=null){ return WCSUtils.crop(coverage, subset); // TODO I hate this classes that do it all } @@ -365,28 +732,31 @@ private GridCoverage2D handleSubsettingExtension(GridCoverage2D coverage, GetCov /** * @param coverage - * @param request + * @param axesInterpolations + * @param extensions2 * @return */ - private GridCoverage2D handleScaling(GridCoverage2D coverage, GetCoverageType request) { + private GridCoverage2D handleScaling(GridCoverage2D coverage, Map extensions, Map axesInterpolations) { // look for scaling extension - final ExtensionType extension = request.getExtension(); - if(extension==null){ + if(extensions==null||extensions.size()==0||!extensions.containsKey("Scaling")){ // NO SCALING return coverage; } // look for a scaling extension - final EList extensions = extension.getContents(); - for(ExtensionItemType extensionItem:extensions){ - try{ - final ScalingPolicy scaling= ScalingPolicy.valueOf(extensionItem.getName());// TODO extract scaling behavior - return scaling.scale(coverage, extensionItem); - } catch (Exception e) { - // eat me - } + final ExtensionItemType extensionItem=extensions.get("Scaling"); + if(extensionItem!=null){ + + // get scaling + ScalingType scaling=(ScalingType) extensionItem.getObjectContent(); + + // instantiate enum + + final ScalingPolicy scalingPolicy= ScalingPolicy.getPolicy(scaling); + return scalingPolicy.scale(coverage, scaling); } + // no scaling return coverage; } @@ -394,24 +764,67 @@ private GridCoverage2D handleScaling(GridCoverage2D coverage, GetCoverageType re /** * @param coverage * @param request + * @param subsettingCRS * @return */ - private GeneralEnvelope extractSubsettingEnvelope(GridCoverage coverage, GetCoverageType request) { - //default - GeneralEnvelope envelope=new GeneralEnvelope(coverage.getEnvelope()); + private GeneralEnvelope extractSubsettingEnvelope( + GridCoverage coverage, + GetCoverageType request, + CoordinateReferenceSystem subsettingCRS) { + //default envelope + final CoordinateReferenceSystem sourceCRS=coverage.getCoordinateReferenceSystem(); + GeneralEnvelope envelope=null; + if(subsettingCRS==null||CRS.equalsIgnoreMetadata(subsettingCRS,sourceCRS)){ + envelope=new GeneralEnvelope(coverage.getEnvelope()); + } else { + // reproject source coverage to subsetting crs for initialization + try { + envelope= CRS.transform( + CRS.findMathTransform(coverage.getCoordinateReferenceSystem(), subsettingCRS), + coverage.getEnvelope()); + envelope.setCoordinateReferenceSystem(subsettingCRS); + } catch (Exception e) { + final WCS20Exception exception= new WCS20Exception( + "Unable to initialize subsetting envelope", + WCS20Exception.WCS20ExceptionCode.SubsettingCrsNotSupported, + subsettingCRS.toWKT()); // TODO extract code + exception.initCause(e); + throw exception; + } + } // check what we need final EList dimensions = request.getDimensionSubset(); if(dimensions==null||dimensions.size()<=0){ return null; } + + // TODO remove when we handle time and elevation if(dimensions.size()>2){ - + throw new WCS20Exception( + "Invalid number of dimensions", + WCS20Exception.WCS20ExceptionCode.InvalidSubsetting,Integer.toString(dimensions.size())); } + + // put aside the dimensions that we have for double checking + final List axesNames = envelopeDimensionsMapper.getAxesNames(envelope, true); + final List foundDimensions= new ArrayList(); + // parse dimensions for(DimensionSubsetType dim:dimensions){ // get basic information final String dimension=dim.getDimension(); // this is the dimension name which we compare to axes abbreviations from geotools + if(dimension==null||dimension.length()<=0||!axesNames.contains(dimension)){//TODO synonyms on axes labels + throw new WCS20Exception("Empty axis label provided",WCS20Exception.WCS20ExceptionCode.InvalidAxisLabel,dimension==null?"Null":dimension); + } + + // did we already do something with this dimension? + if(foundDimensions.contains(dimension)){ + throw new WCS20Exception("Axis label already used during subsetting",WCS20Exception.WCS20ExceptionCode.InvalidAxisLabel,dimension); + } + foundDimensions.add(dimension); + + // now decide what to do final String CRS= dim.getCRS();// TODO HOW DO WE USE THIS??? if(dim instanceof DimensionTrimType){ @@ -420,8 +833,13 @@ private GeneralEnvelope extractSubsettingEnvelope(GridCoverage coverage, GetCove final double low = Double.parseDouble(trim.getTrimLow()); final double high = Double.parseDouble(trim.getTrimHigh()); + final int axisIndex=envelopeDimensionsMapper.getAxisIndex(envelope, dimension); + if(axisIndex<0){ + throw new WCS20Exception("Invalid axis provided",WCS20Exception.WCS20ExceptionCode.InvalidAxisLabel,dimension); + } + // notice how we choose the order of the axes - envelope.setRange(envelopeDimensionsMapper.getAxisIndex(envelope, dimension), low, high); + envelope.setRange(axisIndex, low, high); } else if(dim instanceof DimensionSliceType){ // SLICING @@ -429,18 +847,84 @@ private GeneralEnvelope extractSubsettingEnvelope(GridCoverage coverage, GetCove final String slicePointS = slicing.getSlicePoint(); final double slicePoint=Double.parseDouble(slicePointS); + final int axisIndex=envelopeDimensionsMapper.getAxisIndex(envelope, dimension); + if(axisIndex<0){ + throw new WCS20Exception("Invalid axis provided",WCS20Exception.WCS20ExceptionCode.InvalidAxisLabel,dimension); + } // notice how we choose the order of the axes - envelope.setRange(envelopeDimensionsMapper.getAxisIndex(envelope, dimension), slicePoint, slicePoint); + AffineTransform affineTransform = getAffineTransform(coverage); + final double scale=axisIndex==0?affineTransform.getScaleX():-affineTransform.getScaleY(); + envelope.setRange(axisIndex, slicePoint, slicePoint+scale); } else { - throw new WcsException( + throw new WCS20Exception( "Invalid element found while attempting to parse dimension subsetting request", - WcsExceptionCode.InvalidSubsetting, + WCS20Exception.WCS20ExceptionCode.InvalidSubsetting, dim.getClass().toString()); } } + // + // intersect with original envelope to make sure the subsetting is valid + // + final GeneralEnvelope sourceEnvelope = new GeneralEnvelope(coverage.getEnvelope()); + if(CRS.equalsIgnoreMetadata(envelope, sourceEnvelope.getCoordinateReferenceSystem())){ + envelope.intersect(sourceEnvelope); + envelope.setCoordinateReferenceSystem(coverage.getCoordinateReferenceSystem()); + } else { + // reproject envelope to native crs for cropping + try { + envelope= CRS.transform( + CRS.findMathTransform(subsettingCRS,sourceCRS), + envelope); + envelope.setCoordinateReferenceSystem(sourceCRS); + + // intersect + envelope.intersect(sourceEnvelope); + envelope.setCoordinateReferenceSystem(sourceCRS); + } catch (Exception e) { + final WCS20Exception exception= new WCS20Exception( + "Unable to initialize subsetting envelope", + WCS20Exception.WCS20ExceptionCode.SubsettingCrsNotSupported, + subsettingCRS.toWKT()); // TODO extract code + exception.initCause(e); + throw exception; + } + } + + if(envelope.isEmpty()){ + throw new WCS20Exception( + "Empty intersection after subsetting", + WCS20Exception.WCS20ExceptionCode.InvalidSubsetting,"");// TODO spit our envelope trimmed + } return envelope; } + /** + * Returns the "Sample to geophysics" transform as an affine transform, or {@code null} + * if none. Note that the returned instance may be an immutable one, not necessarly the + * default Java2D implementation. + * + * @param coverage The coverage for which to get the "grid to CRS" affine transform. + * @return The "grid to CRS" affine transform of the given coverage, or {@code null} + * if none or if the transform is not affine. + */ + static AffineTransform getAffineTransform(final Coverage coverage) { + if (coverage instanceof GridCoverage) { + final GridGeometry geometry = ((GridCoverage) coverage).getGridGeometry(); + if (geometry != null) { + final MathTransform gridToCRS; + if (geometry instanceof GridGeometry2D) { + gridToCRS = ((GridGeometry2D) geometry).getGridToCRS(); + } else { + gridToCRS = geometry.getGridToCRS(); + } + if (gridToCRS instanceof AffineTransform) { + return (AffineTransform) gridToCRS; + } + } + } + return null; + } + } diff --git a/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/exception/WCS20Exception.java b/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/exception/WCS20Exception.java index 383c39ce6fa..a9e1dbbe400 100644 --- a/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/exception/WCS20Exception.java +++ b/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/exception/WCS20Exception.java @@ -21,16 +21,34 @@ public class WCS20Exception extends OWS20Exception { */ private static final long serialVersionUID = -6110652531274829497L; - public static class WCSExceptionCode extends OWS20Exception.OWSExceptionCode { + public static class WCS20ExceptionCode extends OWS20Exception.OWSExceptionCode { - public final static OWSExceptionCode NoSuchCoverage = new WCSExceptionCode("NoSuchCoverage", 404); - public final static OWSExceptionCode EmptyCoverageIdList = new WCSExceptionCode("emptyCoverageIdList", 404); - public final static OWSExceptionCode InvalidEncodingSyntax = new WCSExceptionCode("InvalidEncodingSyntax", 400); - public final static OWSExceptionCode InvalidScaleFactor = new WCSExceptionCode("InvalidScaleFactor", 404); - public final static OWSExceptionCode InvalidExtent = new WCSExceptionCode("InvalidExtent", 404); - public final static OWSExceptionCode ScalingAxisUndefined = new WCSExceptionCode("ScalingAxisUndefined", 404); + public final static WCS20ExceptionCode EmptyCoverageIdList = new WCS20ExceptionCode("emptyCoverageIdList", 404); + public final static WCS20ExceptionCode InvalidEncodingSyntax = new WCS20ExceptionCode("InvalidEncodingSyntax", 400); + + // Scaling Extension + public final static WCS20ExceptionCode InvalidScaleFactor = new WCS20ExceptionCode("InvalidScaleFactor", 404); + public final static WCS20ExceptionCode InvalidExtent = new WCS20ExceptionCode("InvalidExtent", 404); + public final static WCS20ExceptionCode ScaleAxisUndefined = new WCS20ExceptionCode("ScaleAxisUndefined", 404); + + + // Interpolation Extension + public final static WCS20ExceptionCode NoSuchAxis = new WCS20ExceptionCode("ScalingAxisUndefined", 404); + public final static WCS20ExceptionCode InterpolationMethodNotSupported = new WCS20ExceptionCode("InterpolationMethodNotSupported", 404); + + // CRS Extension + public final static WCS20ExceptionCode NotACrs = new WCS20ExceptionCode("NotACrs", 404); + public final static WCS20ExceptionCode SubsettingCrsNotSupported = new WCS20ExceptionCode("SubsettingCrs-NotSupported", 404); + public final static WCS20ExceptionCode OutputCrsNotSupported = new WCS20ExceptionCode("OutputCrs-NotSupported", 404); + - protected WCSExceptionCode(String exceptionCode, Integer httpCode) { + // CORE + public final static WCS20ExceptionCode NoSuchCoverage = new WCS20ExceptionCode("NoSuchCoverage", 404); + public final static WCS20ExceptionCode InvalidSubsetting = new WCS20ExceptionCode("InvalidSubsetting", 404); + public final static WCS20ExceptionCode InvalidAxisLabel = new WCS20ExceptionCode("InvalidAxisLabel", 404); + + + protected WCS20ExceptionCode(String exceptionCode, Integer httpCode) { super(exceptionCode, httpCode); } } diff --git a/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/kvp/SubsetKvpParser.java b/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/kvp/SubsetKvpParser.java index ddf21699510..2dbce22155b 100644 --- a/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/kvp/SubsetKvpParser.java +++ b/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/kvp/SubsetKvpParser.java @@ -11,7 +11,7 @@ import org.geoserver.ows.KvpParser; import org.geoserver.platform.OWS20Exception; -import org.geoserver.wcs2_0.exception.WCS20Exception.WCSExceptionCode; +import org.geoserver.wcs2_0.exception.WCS20Exception.WCS20ExceptionCode; /** * Parses the WCS 2.0 subset key @@ -44,7 +44,7 @@ public Object parse(String value) throws Exception { if (openIdx == -1 || closeIdx == -1 || closeIdx < value.length() - 1) { throw new OWS20Exception("Invalid syntax, dimension [ , crs ] ( intervalOrPoint ) is expected", - WCSExceptionCode.InvalidEncodingSyntax, "subset"); + WCS20ExceptionCode.InvalidEncodingSyntax, "subset"); } @@ -61,7 +61,7 @@ public Object parse(String value) throws Exception { crs = dcElements[1]; } else { throw new OWS20Exception("Invalid syntax, dimension [ , crs ] ( intervalOrPoint ) is expected", - WCSExceptionCode.InvalidEncodingSyntax, "subset"); + WCS20ExceptionCode.InvalidEncodingSyntax, "subset"); } // parse the second part, intervalOrPoint @@ -89,7 +89,7 @@ public Object parse(String value) throws Exception { } else { throw new OWS20Exception("Invalid syntax, dimension [ , crs ] ( intervalOrPoint ) " + "where interval or point has either 1 or two elements", - WCSExceptionCode.InvalidEncodingSyntax, "subset"); + WCS20ExceptionCode.InvalidEncodingSyntax, "subset"); } } @@ -102,7 +102,7 @@ private String parsePoint(String point, boolean allowStar) { } else { throw new OWS20Exception( "Invalid usage of *, it can be used only when specifying an interval", - WCSExceptionCode.InvalidEncodingSyntax, "subset"); + WCS20ExceptionCode.InvalidEncodingSyntax, "subset"); } } else if (point.startsWith("\"") && point.endsWith("\"")) { point = point.substring(1, point.length() - 1); @@ -113,7 +113,7 @@ private String parsePoint(String point, boolean allowStar) { } catch (NumberFormatException e) { throw new OWS20Exception("Invalid point value " + point + ", it is not a number and it's not between double quotes", - WCSExceptionCode.InvalidEncodingSyntax, "subset"); + WCS20ExceptionCode.InvalidEncodingSyntax, "subset"); } } diff --git a/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/response/GMLCoverageResponseDelegate.java b/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/response/GMLCoverageResponseDelegate.java index 77b38a083e2..572494b8cfb 100644 --- a/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/response/GMLCoverageResponseDelegate.java +++ b/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/response/GMLCoverageResponseDelegate.java @@ -16,6 +16,8 @@ */ package org.geoserver.wcs2_0.response; +import java.awt.image.DataBuffer; +import java.awt.image.RenderedImage; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; @@ -25,11 +27,14 @@ import javax.measure.unit.Unit; import javax.measure.unit.UnitFormat; +import javax.media.jai.PlanarImage; +import javax.media.jai.iterator.RectIter; +import javax.media.jai.iterator.RectIterFactory; import javax.xml.transform.TransformerException; import org.geoserver.platform.ServiceException; import org.geoserver.wcs.responses.CoverageResponseDelegate; -import org.geoserver.wcs2_0.util.EnvelopeDimensionsMapper; +import org.geoserver.wcs2_0.util.EnvelopeAxesLabelsMapper; import org.geotools.coverage.GridSampleDimension; import org.geotools.coverage.TypeMap; import org.geotools.coverage.grid.GridCoverage2D; @@ -82,9 +87,9 @@ public class GMLCoverageResponseDelegate implements CoverageResponseDelegate { */ private static class GMLTransformer extends TransformerBase{ - private EnvelopeDimensionsMapper envelopeDimensionsMapper; + private EnvelopeAxesLabelsMapper envelopeDimensionsMapper; - public GMLTransformer(EnvelopeDimensionsMapper envelopeDimensionsMapper ) { + public GMLTransformer(EnvelopeAxesLabelsMapper envelopeDimensionsMapper ) { this.envelopeDimensionsMapper=envelopeDimensionsMapper; } @@ -321,7 +326,7 @@ private void handleRange(GridCoverage2D gc2d) { end("gml:rangeParameters"); start("tupleList"); -// // walk through the coverage and spit it out! + // walk through the coverage and spit it out! // final RenderedImage raster= gc2d.getRenderedImage(); // final int numBands=raster.getSampleModel().getNumBands(); // final int dataType=raster.getSampleModel().getDataType(); @@ -411,10 +416,11 @@ private void handleRangeType(GridCoverage2D gc2d) { // get bands final SampleDimension[] bands= gc2d.getSampleDimensions(); - + // handle bands + int i=1; for(SampleDimension sd:bands){ final AttributesImpl fieldAttr = new AttributesImpl(); - fieldAttr.addAttribute("", "name", "name", "", sd.getDescription().toString()); // TODO NCNAME? + fieldAttr.addAttribute("", "name", "name", "", "band"+i++);//sd.getDescription().toString()); // TODO NCNAME? start("swe:field",fieldAttr); start("swe:Quantity"); @@ -619,11 +625,11 @@ public Translator createTranslator(ContentHandler handler) { } /** Can be used to map dimensions name to indexes*/ - private EnvelopeDimensionsMapper envelopeDimensionsMapper; + private EnvelopeAxesLabelsMapper envelopeDimensionsMapper; - public GMLCoverageResponseDelegate(EnvelopeDimensionsMapper envelopeDimensionsMapper) { + public GMLCoverageResponseDelegate(EnvelopeAxesLabelsMapper envelopeDimensionsMapper) { this.envelopeDimensionsMapper=envelopeDimensionsMapper; } diff --git a/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/response/WCS20DescribeCoverageTransformer.java b/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/response/WCS20DescribeCoverageTransformer.java index 596c95c9548..c29aa052ed5 100644 --- a/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/response/WCS20DescribeCoverageTransformer.java +++ b/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/response/WCS20DescribeCoverageTransformer.java @@ -17,7 +17,7 @@ import org.geoserver.wcs.responses.CoverageResponseDelegateFinder; import org.geoserver.wcs2_0.WCS20Const; import org.geoserver.wcs2_0.exception.WCS20Exception; -import org.geoserver.wcs2_0.util.EnvelopeDimensionsMapper; +import org.geoserver.wcs2_0.util.EnvelopeAxesLabelsMapper; import org.geoserver.wcs2_0.util.NCNameResourceCodec; import org.geoserver.wcs2_0.util.StringUtils; import org.geotools.geometry.jts.ReferencedEnvelope; @@ -46,12 +46,12 @@ public class WCS20DescribeCoverageTransformer extends TransformerBase { private CoverageResponseDelegateFinder responseFactory; /** Utility class to map envelope dimension*/ - private EnvelopeDimensionsMapper envelopeDimensionsMapper; + private EnvelopeAxesLabelsMapper envelopeDimensionsMapper; /** * Creates a new WFSCapsTransformer object. */ - public WCS20DescribeCoverageTransformer(WCSInfo wcs, Catalog catalog, CoverageResponseDelegateFinder responseFactory,EnvelopeDimensionsMapper envelopeDimensionsMapper) { + public WCS20DescribeCoverageTransformer(WCSInfo wcs, Catalog catalog, CoverageResponseDelegateFinder responseFactory,EnvelopeAxesLabelsMapper envelopeDimensionsMapper) { this.wcs = wcs; this.catalog = catalog; this.responseFactory = responseFactory; @@ -105,7 +105,7 @@ public void encode(Object o) throws IllegalArgumentException { if( ! badCoverageIds.isEmpty() ) { String mergedIds = StringUtils.merge(badCoverageIds); throw new WCS20Exception("Could not find the requested coverage(s): " + mergedIds - , WCS20Exception.WCSExceptionCode.NoSuchCoverage, mergedIds); + , WCS20Exception.WCS20ExceptionCode.NoSuchCoverage, mergedIds); } // ok: build the response diff --git a/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/util/EnvelopeDimensionsMapper.java b/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/util/EnvelopeAxesLabelsMapper.java similarity index 95% rename from src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/util/EnvelopeDimensionsMapper.java rename to src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/util/EnvelopeAxesLabelsMapper.java index e98d7314a13..f411d4ef3d3 100644 --- a/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/util/EnvelopeDimensionsMapper.java +++ b/src/community/wcs2_0/src/main/java/org/geoserver/wcs2_0/util/EnvelopeAxesLabelsMapper.java @@ -28,6 +28,7 @@ import org.geotools.util.logging.Logging; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.crs.GeographicCRS; import org.opengis.referencing.cs.CoordinateSystem; import org.opengis.referencing.cs.CoordinateSystemAxis; @@ -38,17 +39,17 @@ * TODO caching depending on CRS? * TODO handle composite CRS */ -public class EnvelopeDimensionsMapper { +public class EnvelopeAxesLabelsMapper { /** Logger for this class*/ - private final static Logger LOGGER= Logging.getLogger(EnvelopeDimensionsMapper.class); + private final static Logger LOGGER= Logging.getLogger(EnvelopeAxesLabelsMapper.class); public List getAxesNames(Envelope envelope, boolean swapAxes){ Utilities.ensureNonNull("envelope", envelope); final CoordinateReferenceSystem crs= envelope.getCoordinateReferenceSystem(); // handle axes switch for geographic crs - final boolean axesSwitch= CRS.getAxisOrder(crs)==AxisOrder.EAST_NORTH&&swapAxes; + final boolean axesSwitch= crs instanceof GeographicCRS &&swapAxes; // additional fields for CRS final CoordinateSystem cs=crs.getCoordinateSystem(); diff --git a/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/CRSExtentionTest.java b/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/CRSExtentionTest.java new file mode 100644 index 00000000000..ba2031d6bd2 --- /dev/null +++ b/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/CRSExtentionTest.java @@ -0,0 +1,161 @@ +package org.geoserver.wcs2_0; + +import static junit.framework.Assert.assertEquals; + +import java.io.File; + +import junit.framework.Assert; + +import org.apache.commons.io.FileUtils; +import org.geotools.coverage.grid.GridCoverage2D; +import org.geotools.gce.geotiff.GeoTiffReader; +import org.geotools.geometry.GeneralEnvelope; +import org.geotools.referencing.CRS; +import org.junit.Test; +import org.opengis.coverage.grid.GridEnvelope; +import org.opengis.referencing.crs.CoordinateReferenceSystem; + +import com.mockrunner.mock.web.MockHttpServletResponse; +/** + * + * @author Simone Giannecchini, GeoSolutions SAS + * + */ +public class CRSExtentionTest extends WCSTestSupport { + + @Test + public void testGetCoverageOutputCRSXML() throws Exception { + final File xml= new File("./src/test/resources/requestGetCoverageOutputCRS.xml"); + final String request= FileUtils.readFileToString(xml); + MockHttpServletResponse response = postAsServletResponse("wcs", request); + + assertEquals("image/tiff", response.getContentType()); + byte[] tiffContents = getBinary(response); + File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); + FileUtils.writeByteArrayToFile(file, tiffContents); + + GeoTiffReader readerTarget = new GeoTiffReader(file); + GridCoverage2D targetCoverage = null; + try { + targetCoverage = readerTarget.read(null); + final CoordinateReferenceSystem targetCRS=CRS.decode("EPSG:3857", true); + Assert.assertTrue(CRS.equalsIgnoreMetadata(targetCoverage.getCoordinateReferenceSystem(), targetCRS)); + + // checks + final GridEnvelope gridRange = targetCoverage.getGridGeometry().getGridRange(); + + final GeneralEnvelope expectedEnvelope= new GeneralEnvelope( + new double[]{1.6308305401213994E7,-5543147.203861462}, + new double[]{1.6475284637403902E7,-5311971.846945147}); + expectedEnvelope.setCoordinateReferenceSystem(targetCRS); + + final double scale = getScale(targetCoverage); + assertEnvelopeEquals(expectedEnvelope,scale,(GeneralEnvelope) targetCoverage.getEnvelope(),scale); + assertEquals(gridRange.getSpan(0), 360); + assertEquals(gridRange.getSpan(1), 360); + + } finally { + try{ + readerTarget.dispose(); + } catch (Exception e) { + // TODO: handle exception + } + try{ + scheduleForCleaning(targetCoverage); + } catch (Exception e) { + // TODO: handle exception + } + } + } + + @Test + public void testGetCoverageSubsettingCRSFullXML() throws Exception { + final File xml= new File("./src/test/resources/requestGetCoverageSubsettingCRS.xml"); + final String request= FileUtils.readFileToString(xml); + MockHttpServletResponse response = postAsServletResponse("wcs", request); + + assertEquals("image/tiff", response.getContentType()); + byte[] tiffContents = getBinary(response); + File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); + FileUtils.writeByteArrayToFile(file, tiffContents); + + GeoTiffReader readerTarget = new GeoTiffReader(file); + GridCoverage2D targetCoverage = null; + try { + targetCoverage = readerTarget.read(null); + final CoordinateReferenceSystem targetCRS=CRS.decode("EPSG:3857", true); + Assert.assertTrue(CRS.equalsIgnoreMetadata(targetCoverage.getCoordinateReferenceSystem(), targetCRS)); + + // checks + final GridEnvelope gridRange = targetCoverage.getGridGeometry().getGridRange(); + + final GeneralEnvelope expectedEnvelope= new GeneralEnvelope( + new double[]{1.6308305401213994E7,-5543147.203861462}, + new double[]{1.6475284637403902E7,-5311971.846945147}); + expectedEnvelope.setCoordinateReferenceSystem(targetCRS); + + final double scale = getScale(targetCoverage); + assertEnvelopeEquals(expectedEnvelope,scale,(GeneralEnvelope) targetCoverage.getEnvelope(),scale); + assertEquals(gridRange.getSpan(0), 360); + assertEquals(gridRange.getSpan(1), 360); + + } finally { + try{ + readerTarget.dispose(); + } catch (Exception e) { + // TODO: handle exception + } + try{ + scheduleForCleaning(targetCoverage); + } catch (Exception e) { + // TODO: handle exception + } + } + } + + @Test + public void testGetCoverageSubsettingTrimCRSXML() throws Exception { + final File xml= new File("./src/test/resources/requestGetCoverageSubsettingTrimCRS.xml"); + final String request= FileUtils.readFileToString(xml); + MockHttpServletResponse response = postAsServletResponse("wcs", request); + + assertEquals("image/tiff", response.getContentType()); + byte[] tiffContents = getBinary(response); + File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); + FileUtils.writeByteArrayToFile(file, tiffContents); + + GeoTiffReader readerTarget = new GeoTiffReader(file); + GridCoverage2D targetCoverage = null; + try { + targetCoverage = readerTarget.read(null); + final CoordinateReferenceSystem targetCRS=CRS.decode("EPSG:3857", true); + Assert.assertTrue(CRS.equalsIgnoreMetadata(targetCoverage.getCoordinateReferenceSystem(), targetCRS)); + + // checks + final GridEnvelope gridRange = targetCoverage.getGridGeometry().getGridRange(); + + final GeneralEnvelope expectedEnvelope= new GeneralEnvelope( + new double[]{1.6308305401213994E7,-5543147.203861462}, + new double[]{1.6475284637403902E7,-5311971.846945147}); + expectedEnvelope.setCoordinateReferenceSystem(targetCRS); + + final double scale = getScale(targetCoverage); + assertEnvelopeEquals(expectedEnvelope,scale,(GeneralEnvelope) targetCoverage.getEnvelope(),scale); + assertEquals(gridRange.getSpan(0), 360); + assertEquals(gridRange.getSpan(1), 360); + + } finally { + try{ + readerTarget.dispose(); + } catch (Exception e) { + // TODO: handle exception + } + try{ + scheduleForCleaning(targetCoverage); + } catch (Exception e) { + // TODO: handle exception + } + } + } + +} diff --git a/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/GetCoverageTest.java b/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/GetCoverageTest.java index a5cd733d607..8bb5e86cb06 100644 --- a/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/GetCoverageTest.java +++ b/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/GetCoverageTest.java @@ -2,20 +2,26 @@ import static junit.framework.Assert.assertEquals; -import it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReader; -import it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReaderSpi; - import java.io.File; -import javax.imageio.stream.FileImageInputStream; +import junit.framework.Assert; import org.apache.commons.io.FileUtils; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.gce.geotiff.GeoTiffReader; +import org.geotools.geometry.GeneralEnvelope; +import org.geotools.referencing.CRS; import org.junit.Test; +import org.opengis.coverage.grid.GridEnvelope; import com.mockrunner.mock.web.MockHttpServletResponse; - +/** + * Testing WCS 2.0 Core {@link GetCoverage} + * + * @author Simone Giannecchini, GeoSolutions SAS + * @author Emanuele Tajariol, GeoSolutions SAS + * + */ public class GetCoverageTest extends WCSTestSupport { @Test @@ -24,326 +30,370 @@ public void testGetMissingCoverage() throws Exception { checkOws20Exception(response, 404, "NoSuchCoverage", "coverageId"); } + + /** + * Trimming only on Longitude + * + * @throws Exception + */ @Test - public void testReprojectXML() throws Exception { - String request = "\n" + "\n" - + " wcs__BlueMarble\n" - + " \n" - + " Long\n" - + " 146.5\n" - + " 147.0\n" - + " \n" - + " \n" - + " Lat\n" - + " -43.5\n" - + " -43.0\n" - + " \n" - + " \n" - + " " - + " http://www.opengis.net/def/crs/EPSG/0/4326" - + " \n" - + " \n" - + " image/tiff\n" - + ""; - + public void testCoverageTrimmingLatitudeNativeCRSXML() throws Exception { + final File xml= new File("./src/test/resources/requestGetCoverageTrimmingLatitudeNativeCRSXML.xml"); + final String request= FileUtils.readFileToString(xml); MockHttpServletResponse response = postAsServletResponse("wcs", request); - + assertEquals("image/tiff", response.getContentType()); byte[] tiffContents = getBinary(response); File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); FileUtils.writeByteArrayToFile(file, tiffContents); - // TODO: check the tiff structure is the one requested - final TIFFImageReader reader = (TIFFImageReader) new TIFFImageReaderSpi() - .createReaderInstance(); - reader.setInput(new FileImageInputStream(file)); + GeoTiffReader readerTarget = new GeoTiffReader(file); + GridCoverage2D targetCoverage = null; + try { + targetCoverage = readerTarget.read(null); + + // checks + final GridEnvelope gridRange = targetCoverage.getGridGeometry().getGridRange(); + + final GeneralEnvelope expectedEnvelope= new GeneralEnvelope( + new double[]{targetCoverage.getEnvelope().getMinimum(0),-43.5}, + new double[]{targetCoverage.getEnvelope().getMaximum(0),-43.0}); + expectedEnvelope.setCoordinateReferenceSystem(CRS.decode("EPSG:4326", true)); + + final double scale = getScale(targetCoverage); + assertEnvelopeEquals(expectedEnvelope,scale,(GeneralEnvelope) targetCoverage.getEnvelope(),scale); + Assert.assertTrue(CRS.equalsIgnoreMetadata(targetCoverage.getCoordinateReferenceSystem(), expectedEnvelope.getCoordinateReferenceSystem())); + assertEquals(gridRange.getSpan(0), 360); + assertEquals(gridRange.getSpan(1), 120); + + } finally { + try{ + readerTarget.dispose(); + } catch (Exception e) { + // TODO: handle exception + } + try{ + scheduleForCleaning(targetCoverage); + } catch (Exception e) { + // TODO: handle exception + } + } } @Test - public void testTrimmingCoverageXML() throws Exception { - String request = "\n" + "\n" - + " wcs__BlueMarble\n" - + " \n" - + " Long\n" - + " 146.5\n" - + " 147.0\n" - + " \n" - + " \n" - + " Lat\n" - + " -43.5\n" - + " -43.0\n" - + " \n" - + " \n" - + " " - + " http://www.opengis.net/def/crs/EPSG/0/4326" - + " \n" - + " \n" - + " image/tiff\n" - + ""; - + public void testCoverageTrimmingNativeCRSXML() throws Exception { + final File xml= new File("./src/test/resources/requestGetCoverageTrimmingNativeCRSXML.xml"); + final String request= FileUtils.readFileToString(xml); MockHttpServletResponse response = postAsServletResponse("wcs", request); - + assertEquals("image/tiff", response.getContentType()); byte[] tiffContents = getBinary(response); File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); FileUtils.writeByteArrayToFile(file, tiffContents); - // TODO: check the tiff structure is the one requested - final TIFFImageReader reader = (TIFFImageReader) new TIFFImageReaderSpi() - .createReaderInstance(); - reader.setInput(new FileImageInputStream(file)); + GeoTiffReader readerTarget = new GeoTiffReader(file); + GridCoverage2D targetCoverage = null; + try { + targetCoverage = readerTarget.read(null); + + // checks + final GridEnvelope gridRange = targetCoverage.getGridGeometry().getGridRange(); + + final GeneralEnvelope expectedEnvelope= new GeneralEnvelope( + new double[]{146.5,-43.5}, + new double[]{147.0,-43.0}); + expectedEnvelope.setCoordinateReferenceSystem(CRS.decode("EPSG:4326", true)); + + final double scale = getScale(targetCoverage); + assertEnvelopeEquals(expectedEnvelope,scale,(GeneralEnvelope) targetCoverage.getEnvelope(),scale); + Assert.assertTrue(CRS.equalsIgnoreMetadata(targetCoverage.getCoordinateReferenceSystem(), expectedEnvelope.getCoordinateReferenceSystem())); + assertEquals(gridRange.getSpan(0), 120); + assertEquals(gridRange.getSpan(1), 120); + + } finally { + try{ + readerTarget.dispose(); + } catch (Exception e) { + // TODO: handle exception + } + try{ + scheduleForCleaning(targetCoverage); + } catch (Exception e) { + // TODO: handle exception + } + } } @Test - public void testScaleFactorIndividualXML() throws Exception { - String request = "\n" + "\n" - + " wcs__BlueMarble\n" - + " \n" - + " " - + " " - + " " - + " http://www.opengis.net/def/axis/OGC/1/i" - + " " - + " 3.5" - + " " - + " " - + " " - + " http://www.opengis.net/def/axis/OGC/1/j" - + " " - + " 3.5" - + " " - + " " - + " " - + " http://www.opengis.net/def/axis/OGC/1/k" - + " " - + " 2.0" - + " " - + " " - + " \n" - + " image/tiff\n" - + ""; - + public void testGetFullCoverageKVP() throws Exception { + MockHttpServletResponse response = getAsServletResponse("wcs?request=GetCoverage&service=WCS&version=2.0.1&coverageId=wcs__BlueMarble"); + + assertEquals("image/tiff", response.getContentType()); + byte[] tiffContents = getBinary(response); + File file = new File("./target/bm_full.tiff"); + FileUtils.writeByteArrayToFile(file, tiffContents); + + GeoTiffReader readerTarget = new GeoTiffReader(file); + GridCoverage2D targetCoverage = null, sourceCoverage=null; + try { + targetCoverage = readerTarget.read(null); + sourceCoverage=(GridCoverage2D) this.getCatalog().getCoverageByName("BlueMarble").getGridCoverageReader(null, null).read(null); + + // checks + assertEquals(sourceCoverage.getGridGeometry().getGridRange(), targetCoverage.getGridGeometry().getGridRange()); + assertEquals(sourceCoverage.getCoordinateReferenceSystem(), targetCoverage.getCoordinateReferenceSystem()); + assertEquals(sourceCoverage.getEnvelope(), targetCoverage.getEnvelope()); + } finally { + try{ + readerTarget.dispose(); + } catch (Exception e) { + // TODO: handle exception + } + try{ + scheduleForCleaning(targetCoverage); + } catch (Exception e) { + // TODO: handle exception + } + try{ + scheduleForCleaning(sourceCoverage); + } catch (Exception e) { + // TODO: handle exception + } + } + } + + @Test + public void testGetFullCoverageXML() throws Exception { + final File xml= new File("./src/test/resources/requestGetFullCoverage.xml"); + final String request= FileUtils.readFileToString(xml); MockHttpServletResponse response = postAsServletResponse("wcs", request); - + assertEquals("image/tiff", response.getContentType()); byte[] tiffContents = getBinary(response); File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); FileUtils.writeByteArrayToFile(file, tiffContents); + + // check we can read it as a TIFF and it is similare to the origina one + GeoTiffReader readerTarget = new GeoTiffReader(file); + GridCoverage2D targetCoverage = null, sourceCoverage=null; + try { + targetCoverage = readerTarget.read(null); + sourceCoverage=(GridCoverage2D) this.getCatalog().getCoverageByName("BlueMarble").getGridCoverageReader(null, null).read(null); + + // checks + assertEquals(sourceCoverage.getGridGeometry().getGridRange(), targetCoverage.getGridGeometry().getGridRange()); + assertEquals(sourceCoverage.getCoordinateReferenceSystem(), targetCoverage.getCoordinateReferenceSystem()); + assertEquals(sourceCoverage.getEnvelope(), targetCoverage.getEnvelope()); + } finally { + try{ + readerTarget.dispose(); + } catch (Exception e) { + // TODO: handle exception + } + try{ + scheduleForCleaning(targetCoverage); + } catch (Exception e) { + // TODO: handle exception + } + try{ + scheduleForCleaning(sourceCoverage); + } catch (Exception e) { + // TODO: handle exception + } + } + } - // TODO: check the tiff structure is the one requested - final TIFFImageReader reader = (TIFFImageReader) new TIFFImageReaderSpi() - .createReaderInstance(); - reader.setInput(new FileImageInputStream(file)); - } + /** + * Trimming only on Longitude + * + * @throws Exception + */ @Test - public void testScaleSizeIndividualXML() throws Exception { - String request = "\n" + "\n" - + " wcs__BlueMarble\n" - + " \n" - + " \n" - + " \n" - + " \n" - + " http://www.opengis.net/def/axis/OGC/1/i\n" - + " \n" - + " 10\n" - + " 20\n" - + " \n" - + " \n" - + " \n" - + " http://www.opengis.net/def/axis/OGC/1/j\n" - + " \n" - + " 20\n" - + " 30\n" - + " \n" - + " \n" - + " image/tiff\n" - + " \n" - + ""; + public void testCoverageTrimmingLongitudeNativeCRSXML() throws Exception { + final File xml= new File("./src/test/resources/requestGetCoverageTrimmingLongNativeCRSXML.xml"); + final String request= FileUtils.readFileToString(xml); MockHttpServletResponse response = postAsServletResponse("wcs", request); - + assertEquals("image/tiff", response.getContentType()); byte[] tiffContents = getBinary(response); File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); FileUtils.writeByteArrayToFile(file, tiffContents); - - // TODO: check the tiff structure is the one requested - final TIFFImageReader reader = (TIFFImageReader) new TIFFImageReaderSpi() - .createReaderInstance(); - reader.setInput(new FileImageInputStream(file)); + + GeoTiffReader readerTarget = new GeoTiffReader(file); + GridCoverage2D targetCoverage = null; + try { + targetCoverage = readerTarget.read(null); + + // checks + final GridEnvelope gridRange = targetCoverage.getGridGeometry().getGridRange(); + + final GeneralEnvelope expectedEnvelope= new GeneralEnvelope( + new double[]{146.5,targetCoverage.getEnvelope().getMinimum(1)}, + new double[]{147.0,targetCoverage.getEnvelope().getMaximum(1)}); + expectedEnvelope.setCoordinateReferenceSystem(CRS.decode("EPSG:4326", true)); + + final double scale = getScale(targetCoverage); + assertEnvelopeEquals(expectedEnvelope,scale,(GeneralEnvelope) targetCoverage.getEnvelope(),scale); + Assert.assertTrue(CRS.equalsIgnoreMetadata(targetCoverage.getCoordinateReferenceSystem(), expectedEnvelope.getCoordinateReferenceSystem())); + assertEquals(gridRange.getSpan(0), 120); + assertEquals(gridRange.getSpan(1), 360); + + } finally { + try{ + readerTarget.dispose(); + } catch (Exception e) { + // TODO: handle exception + } + try{ + scheduleForCleaning(targetCoverage); + } catch (Exception e) { + // TODO: handle exception + } + } } - @Test - public void testScaleExtentIndividualXML() throws Exception { - String request = "\n" + "\n" - + " wcs__BlueMarble\n" - + " \n" - + " \n" - + " \n" - + " \n" - + " http://www.opengis.net/def/axis/OGC/1/i\n" - + " \n" - + " 10\n" - + " 20\n" - + " \n" - + " \n" - + " \n" - + " http://www.opengis.net/def/axis/OGC/1/j\n" - + " \n" - + " 20\n" - + " 30\n" - + " \n" - + " \n" - + " \n" - + " image/tiff\n" - + ""; - + public void testCoverageTrimmingSlicingNativeCRSXML() throws Exception { + final File xml= new File("./src/test/resources/requestGetCoverageTrimmingSlicingNativeCRSXML.xml"); + final String request= FileUtils.readFileToString(xml); MockHttpServletResponse response = postAsServletResponse("wcs", request); - + assertEquals("image/tiff", response.getContentType()); byte[] tiffContents = getBinary(response); File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); FileUtils.writeByteArrayToFile(file, tiffContents); - - // TODO: check the tiff structure is the one requested - final TIFFImageReader reader = (TIFFImageReader) new TIFFImageReaderSpi() - .createReaderInstance(); - reader.setInput(new FileImageInputStream(file)); - } + + GeoTiffReader readerTarget = new GeoTiffReader(file); + GridCoverage2D targetCoverage = null; + try { + targetCoverage = readerTarget.read(null); + + // checks + final GridEnvelope gridRange = targetCoverage.getGridGeometry().getGridRange(); + + // 1 dimensional slice along latitude + final GeneralEnvelope expectedEnvelope= new GeneralEnvelope( + new double[]{146.49999999999477,-43.5}, + new double[]{146.99999999999477,-43.49583333333119}); + expectedEnvelope.setCoordinateReferenceSystem(CRS.decode("EPSG:4326", true)); + + final double scale = getScale(targetCoverage); + assertEnvelopeEquals(expectedEnvelope,scale,(GeneralEnvelope) targetCoverage.getEnvelope(),scale); + Assert.assertTrue(CRS.equalsIgnoreMetadata(targetCoverage.getCoordinateReferenceSystem(), expectedEnvelope.getCoordinateReferenceSystem())); + assertEquals(gridRange.getSpan(1), 1); + assertEquals(gridRange.getSpan(0), 120); + + } finally { + try{ + readerTarget.dispose(); + } catch (Exception e) { + // TODO: handle exception + } + try{ + scheduleForCleaning(targetCoverage); + } catch (Exception e) { + // TODO: handle exception + } + } + } @Test - public void testScaleFactorXML() throws Exception { - String request = "\n" + "\n" - + " wcs__BlueMarble\n" - + " \n" - + " " - + " 2.0" - + " \n" - + " \n" - + " image/tiff\n" - + ""; - + public void testCoverageTrimmingDuplicatedNativeCRSXML() throws Exception { + final File xml= new File("./src/test/resources/requestGetCoverageTrimmingDuplicatedNativeCRSXML.xml"); + final String request= FileUtils.readFileToString(xml); MockHttpServletResponse response = postAsServletResponse("wcs", request); + + assertEquals("application/xml", response.getContentType()); +// checkOws20Exception(response, 404, "InvalidAxisLabel", "coverageId"); + + } + @Test + public void testCoverageSlicingLongitudeNativeCRSXML() throws Exception { + final File xml= new File("./src/test/resources/requestGetCoverageSlicingLongitudeNativeCRSXML.xml"); + final String request= FileUtils.readFileToString(xml); + MockHttpServletResponse response = postAsServletResponse("wcs", request); + assertEquals("image/tiff", response.getContentType()); byte[] tiffContents = getBinary(response); File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); FileUtils.writeByteArrayToFile(file, tiffContents); - - // TODO: check the tiff structure is the one requested - final TIFFImageReader reader = (TIFFImageReader) new TIFFImageReaderSpi() - .createReaderInstance(); - reader.setInput(new FileImageInputStream(file)); - } + GeoTiffReader readerTarget = new GeoTiffReader(file); + GridCoverage2D targetCoverage = null; + try { + targetCoverage = readerTarget.read(null); + + // checks + final GridEnvelope gridRange = targetCoverage.getGridGeometry().getGridRange(); + + // 1 dimensional slice along longitude + final GeneralEnvelope expectedEnvelope= new GeneralEnvelope( + new double[]{146.5,-44.49999999999784}, + new double[]{146.50416666666143,-42.99999999999787}); + expectedEnvelope.setCoordinateReferenceSystem(CRS.decode("EPSG:4326", true)); + + final double scale = getScale(targetCoverage); + assertEnvelopeEquals(expectedEnvelope,scale,(GeneralEnvelope) targetCoverage.getEnvelope(),scale); + Assert.assertTrue(CRS.equalsIgnoreMetadata(targetCoverage.getCoordinateReferenceSystem(), expectedEnvelope.getCoordinateReferenceSystem())); + assertEquals(gridRange.getSpan(0), 1); + assertEquals(gridRange.getSpan(1), 360); + + } finally { + try{ + readerTarget.dispose(); + } catch (Exception e) { + // TODO: handle exception + } + try{ + scheduleForCleaning(targetCoverage); + } catch (Exception e) { + // TODO: handle exception + } + } + } + @Test - public void testGetFullCoverage() throws Exception { - MockHttpServletResponse response = getAsServletResponse("wcs?request=GetCoverage&service=WCS&version=2.0.1&coverageId=wcs__BlueMarble"); + public void testCoverageSlicingLatitudeNativeCRSXML() throws Exception { + final File xml= new File("./src/test/resources/requestGetCoverageSlicingLatitudeNativeCRSXML.xml"); + final String request= FileUtils.readFileToString(xml); + MockHttpServletResponse response = postAsServletResponse("wcs", request); assertEquals("image/tiff", response.getContentType()); byte[] tiffContents = getBinary(response); - File file = new File("./target/bm_full.tiff"); + File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); FileUtils.writeByteArrayToFile(file, tiffContents); - - // check we can read it as a TIFF - GeoTiffReader reader = new GeoTiffReader(file); - GridCoverage2D coverage = null; + + GeoTiffReader readerTarget = new GeoTiffReader(file); + GridCoverage2D targetCoverage = null; try { - coverage = reader.read(null); + targetCoverage = readerTarget.read(null); + + // checks + final GridEnvelope gridRange = targetCoverage.getGridGeometry().getGridRange(); + + // 1 dimensional slice along latitude + final GeneralEnvelope expectedEnvelope= new GeneralEnvelope( + new double[]{146.49999999999477,-43.499999999997854}, + new double[]{147.99999999999474,-43.49583333333119}); + expectedEnvelope.setCoordinateReferenceSystem(CRS.decode("EPSG:4326", true)); + + final double scale = getScale(targetCoverage); + assertEnvelopeEquals(expectedEnvelope,scale,(GeneralEnvelope) targetCoverage.getEnvelope(),scale); + Assert.assertTrue(CRS.equalsIgnoreMetadata(targetCoverage.getCoordinateReferenceSystem(), expectedEnvelope.getCoordinateReferenceSystem())); + assertEquals(gridRange.getSpan(1), 1); + assertEquals(gridRange.getSpan(0), 360); + } finally { - reader.dispose(); - scheduleForCleaning(coverage); + try{ + readerTarget.dispose(); + } catch (Exception e) { + // TODO: handle exception + } + try{ + scheduleForCleaning(targetCoverage); + } catch (Exception e) { + // TODO: handle exception + } } - - // TODO: add more checks, make sure we returned the whole thing } - - // TODO: add tests for range subsetting -// -// -// C0001 -// -// -// -// band1 -// -// -// -// band3 -// band5 -// -// -// -// -// - } diff --git a/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/InterpolationExtentionsTest.java b/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/InterpolationExtentionsTest.java new file mode 100644 index 00000000000..03dc6368a9a --- /dev/null +++ b/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/InterpolationExtentionsTest.java @@ -0,0 +1,124 @@ +package org.geoserver.wcs2_0; + +import static junit.framework.Assert.assertEquals; +import it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReader; +import it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReaderSpi; + +import java.io.ByteArrayInputStream; +import java.io.File; + +import javax.imageio.stream.FileImageInputStream; + +import org.apache.commons.io.FileUtils; +import org.junit.Test; +import org.w3c.dom.Document; + +import com.mockrunner.mock.web.MockHttpServletResponse; +/** + * + * @author Simone Giannecchini, GeoSolutions + * + */ +public class InterpolationExtentionsTest extends WCSTestSupport { + + @Test + public void testInterpolationSingleLinearXML() throws Exception { + + final File xml= new File("./src/test/resources/requestGetCoverageInterpolationLinear.xml"); + final String request= FileUtils.readFileToString(xml); + MockHttpServletResponse response = postAsServletResponse("wcs", request); + + assertEquals("image/tiff", response.getContentType()); + byte[] tiffContents = getBinary(response); + File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); + FileUtils.writeByteArrayToFile(file, tiffContents); + + // TODO: check the tiff structure is the one requested + final TIFFImageReader reader = (TIFFImageReader) new TIFFImageReaderSpi() + .createReaderInstance(); + reader.setInput(new FileImageInputStream(file)); + assertEquals(360, reader.getWidth(0)); + assertEquals(360, reader.getHeight(0)); + reader.dispose(); + } + + @Test + public void testInterpolationSingleNearestXML() throws Exception { + + final File xml= new File("./src/test/resources/requestGetCoverageInterpolationNearest.xml"); + final String request= FileUtils.readFileToString(xml); + MockHttpServletResponse response = postAsServletResponse("wcs", request); + + assertEquals("image/tiff", response.getContentType()); + byte[] tiffContents = getBinary(response); + File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); + FileUtils.writeByteArrayToFile(file, tiffContents); + + // TODO: check the tiff structure is the one requested + final TIFFImageReader reader = (TIFFImageReader) new TIFFImageReaderSpi() + .createReaderInstance(); + reader.setInput(new FileImageInputStream(file)); + assertEquals(360, reader.getWidth(0)); + assertEquals(360, reader.getHeight(0)); + reader.dispose(); + } + + @Test + public void testInterpolationMixedSupportedXML() throws Exception { + + final File xml= new File("./src/test/resources/requestGetCoverageInterpolationMixedSupported.xml"); + final String request= FileUtils.readFileToString(xml); + MockHttpServletResponse response = postAsServletResponse("wcs", request); + + assertEquals("image/tiff", response.getContentType()); + byte[] tiffContents = getBinary(response); + File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); + FileUtils.writeByteArrayToFile(file, tiffContents); + + // TODO: check the tiff structure is the one requested + final TIFFImageReader reader = (TIFFImageReader) new TIFFImageReaderSpi() + .createReaderInstance(); + reader.setInput(new FileImageInputStream(file)); + assertEquals(360, reader.getWidth(0)); + assertEquals(360, reader.getHeight(0)); + reader.dispose(); + } + + @Test + public void testInterpolationMixedTimeXML() throws Exception { + + final File xml= new File("./src/test/resources/requestGetCoverageInterpolationMixedTime.xml"); + final String request= FileUtils.readFileToString(xml); + MockHttpServletResponse response = postAsServletResponse("wcs", request); + + assertEquals("application/xml", response.getContentType()); + Document dom = dom(new ByteArrayInputStream(response.getOutputStreamContent().getBytes())); + print(dom); + } + + @Test + public void testInterpolationMixedUnsupportedXML() throws Exception { + + final File xml= new File("./src/test/resources/requestGetCoverageInterpolationMixedUnsupported.xml"); + final String request= FileUtils.readFileToString(xml); + MockHttpServletResponse response = postAsServletResponse("wcs", request); + + assertEquals("application/xml", response.getContentType()); + Document dom = dom(new ByteArrayInputStream(response.getOutputStreamContent().getBytes())); + print(dom); + } + + @Test + public void testInterpolationMixedDuplicatedXML() throws Exception { + + final File xml= new File("./src/test/resources/requestGetCoverageInterpolationMixedDuplicated.xml"); + final String request= FileUtils.readFileToString(xml); + MockHttpServletResponse response = postAsServletResponse("wcs", request); + + assertEquals("application/xml", response.getContentType()); + Document dom = dom(new ByteArrayInputStream(response.getOutputStreamContent().getBytes())); + print(dom); + } + + +} diff --git a/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/RangeSubsetExtentionTest.java b/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/RangeSubsetExtentionTest.java new file mode 100644 index 00000000000..c0c4c6ebf9b --- /dev/null +++ b/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/RangeSubsetExtentionTest.java @@ -0,0 +1,45 @@ +package org.geoserver.wcs2_0; + +import static junit.framework.Assert.assertEquals; +import it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReader; +import it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReaderSpi; + +import java.io.File; + +import javax.imageio.stream.FileImageInputStream; + +import org.apache.commons.io.FileUtils; +import org.junit.Test; + +import com.mockrunner.mock.web.MockHttpServletResponse; +/** + * Testing range subsetting capabilities + * + * @author Simone Giannecchini, GeoSolutions + * + */ +public class RangeSubsetExtentionTest extends WCSTestSupport { + + @Test + public void testBasic() throws Exception { + + final File xml= new File("./src/test/resources/requestGetCoverageRangeSubsetting.xml"); + final String request= FileUtils.readFileToString(xml); + MockHttpServletResponse response = postAsServletResponse("wcs", request); + + assertEquals("image/tiff", response.getContentType()); + byte[] tiffContents = getBinary(response); + File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); + FileUtils.writeByteArrayToFile(file, tiffContents); + + // TODO: check the tiff structure is the one requested + final TIFFImageReader reader = (TIFFImageReader) new TIFFImageReaderSpi() + .createReaderInstance(); + reader.setInput(new FileImageInputStream(file)); + assertEquals(360, reader.getWidth(0)); + assertEquals(360, reader.getHeight(0)); + reader.dispose(); + } + + +} diff --git a/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/ScalingExtentionTest.java b/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/ScalingExtentionTest.java new file mode 100644 index 00000000000..0a0be76b0fe --- /dev/null +++ b/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/ScalingExtentionTest.java @@ -0,0 +1,123 @@ +package org.geoserver.wcs2_0; + +import static junit.framework.Assert.assertEquals; + +import java.io.File; + +import junit.framework.Assert; + +import org.apache.commons.io.FileUtils; +import org.geotools.gce.geotiff.GeoTiffReader; +import org.geotools.referencing.CRS; +import org.junit.Test; + +import com.mockrunner.mock.web.MockHttpServletResponse; +/** + * Testing Scaling Extension + * + * @author Simone Giannecchini, GeoSolution SAS + * + */ +public class ScalingExtentionTest extends WCSTestSupport { + + @Test + public void testScaleAxesByFactorXML() throws Exception { + final File xml= new File("./src/test/resources/requestGetCoverageScaleAxesByFactor.xml"); + final String request= FileUtils.readFileToString(xml); + MockHttpServletResponse response = postAsServletResponse("wcs", request); + + assertEquals("image/tiff", response.getContentType()); + byte[] tiffContents = getBinary(response); + File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); + FileUtils.writeByteArrayToFile(file, tiffContents); + + // check the tiff structure is the one requested + final GeoTiffReader reader = new GeoTiffReader(file); + Assert.assertTrue(CRS.equalsIgnoreMetadata(reader.getCrs(), CRS.decode("EPSG:4326",true))); + assertEquals(1260, reader.getOriginalGridRange().getSpan(0)); + assertEquals(1260, reader.getOriginalGridRange().getSpan(1)); + reader.dispose(); + } + @Test + public void testScaleToSizeXML() throws Exception { + + final File xml= new File("./src/test/resources/requestGetCoverageScaleToSize.xml"); + final String request= FileUtils.readFileToString(xml); + MockHttpServletResponse response = postAsServletResponse("wcs", request); + + assertEquals("image/tiff", response.getContentType()); + byte[] tiffContents = getBinary(response); + File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); + FileUtils.writeByteArrayToFile(file, tiffContents); + + // check the tiff structure is the one requested + final GeoTiffReader reader = new GeoTiffReader(file); + Assert.assertTrue(CRS.equalsIgnoreMetadata(reader.getCrs(), CRS.decode("EPSG:4326",true))); + assertEquals(1000, reader.getOriginalGridRange().getSpan(0)); + assertEquals(1000, reader.getOriginalGridRange().getSpan(1)); + reader.dispose(); + } + + @Test + public void testScaleToExtentXML() throws Exception { + final File xml= new File("./src/test/resources/requestGetCoverageScaleToExtent.xml"); + final String request= FileUtils.readFileToString(xml); + MockHttpServletResponse response = postAsServletResponse("wcs", request); + + assertEquals("image/tiff", response.getContentType()); + byte[] tiffContents = getBinary(response); + File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); + FileUtils.writeByteArrayToFile(file, tiffContents); + + // check the tiff structure is the one requested + final GeoTiffReader reader = new GeoTiffReader(file); + Assert.assertTrue(CRS.equalsIgnoreMetadata(reader.getCrs(), CRS.decode("EPSG:4326",true))); + assertEquals(200, reader.getOriginalGridRange().getSpan(0)); + assertEquals(300, reader.getOriginalGridRange().getSpan(1)); + reader.dispose(); + } + + @Test + public void testScaleByFactorXML() throws Exception { + + final File xml= new File("./src/test/resources/requestGetCoverageScaleByFactor.xml"); + final String request= FileUtils.readFileToString(xml); + MockHttpServletResponse response = postAsServletResponse("wcs", request); + + assertEquals("image/tiff", response.getContentType()); + byte[] tiffContents = getBinary(response); + File file = File.createTempFile("bm_gtiff", "bm_gtiff.tiff", new File("./target")); + FileUtils.writeByteArrayToFile(file, tiffContents); + + // check the tiff structure is the one requested + final GeoTiffReader reader = new GeoTiffReader(file); + Assert.assertTrue(CRS.equalsIgnoreMetadata(reader.getCrs(), CRS.decode("EPSG:4326",true))); + assertEquals(900, reader.getOriginalGridRange().getSpan(0)); + assertEquals(900, reader.getOriginalGridRange().getSpan(1)); + reader.dispose(); + } + + // TODO: add tests for range subsetting +// +// +// C0001 +// +// +// +// band1 +// +// +// +// band3 +// band5 +// +// +// +// +// + +} diff --git a/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/WCSTestSupport.java b/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/WCSTestSupport.java index c499b0ed122..a23793f7d0c 100644 --- a/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/WCSTestSupport.java +++ b/src/community/wcs2_0/src/test/java/org/geoserver/wcs2_0/WCSTestSupport.java @@ -4,6 +4,7 @@ */ package org.geoserver.wcs2_0; +import java.awt.geom.AffineTransform; import java.io.File; import java.net.URL; import java.util.ArrayList; @@ -28,11 +29,17 @@ import org.geoserver.test.GeoServerSystemTestSupport; import org.geoserver.wcs.CoverageCleanerCallback; import org.geoserver.wcs.WCSInfo; +import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.data.DataUtilities; +import org.geotools.geometry.GeneralEnvelope; +import org.geotools.referencing.operation.matrix.XAffineTransform; import org.geotools.wcs.v2_0.WCSConfiguration; import org.geotools.xml.Parser; import org.junit.After; +import org.opengis.coverage.Coverage; import org.opengis.coverage.grid.GridCoverage; +import org.opengis.coverage.grid.GridGeometry; +import org.opengis.referencing.operation.MathTransform; import org.w3c.dom.Document; import org.w3c.dom.ls.LSInput; import org.w3c.dom.ls.LSResourceResolver; @@ -54,6 +61,13 @@ public abstract class WCSTestSupport extends GeoServerSystemTestSupport { List coverages = new ArrayList(); + /** + * Small value for comparaison of sample values. Since most grid coverage implementations in + * Geotools 2 store geophysics values as {@code float} numbers, this {@code EPS} value must + * be of the order of {@code float} relative precision, not {@code double}. + */ + static final float EPS = 1E-5f; + static { final Map namespaceMap = new HashMap() { { @@ -193,5 +207,69 @@ public void cleanCoverages() { } } + /** + * Compares the envelopes of two coverages for equality using the smallest + * scale factor of their "grid to world" transform as the tolerance. + * + * @param expected The coverage having the expected envelope. + * @param actual The coverage having the actual envelope. + */ + static void assertEnvelopeEquals(Coverage expected, Coverage actual) { + final double scaleA = getScale(expected); + final double scaleB = getScale(actual); + + assertEnvelopeEquals((GeneralEnvelope)expected.getEnvelope(),scaleA,(GeneralEnvelope)actual.getEnvelope(),scaleB); + } + + static void assertEnvelopeEquals(GeneralEnvelope expected,double scaleExpected, GeneralEnvelope actual,double scaleActual) { + final double tolerance; + if (scaleExpected <= scaleActual) { + tolerance = scaleExpected*1E-1; + } else if (!Double.isNaN(scaleActual)) { + tolerance = scaleActual*1E-1; + } else { + tolerance = EPS; + } + Assert.assertTrue(expected.equals(actual, tolerance, false)); + } + + /** + * Returns the "Sample to geophysics" transform as an affine transform, or {@code null} + * if none. Note that the returned instance may be an immutable one, not necessarly the + * default Java2D implementation. + * + * @param coverage The coverage for which to get the "grid to CRS" affine transform. + * @return The "grid to CRS" affine transform of the given coverage, or {@code null} + * if none or if the transform is not affine. + */ + static AffineTransform getAffineTransform(final Coverage coverage) { + if (coverage instanceof GridCoverage) { + final GridGeometry geometry = ((GridCoverage) coverage).getGridGeometry(); + if (geometry != null) { + final MathTransform gridToCRS; + if (geometry instanceof GridGeometry2D) { + gridToCRS = ((GridGeometry2D) geometry).getGridToCRS(); + } else { + gridToCRS = geometry.getGridToCRS(); + } + if (gridToCRS instanceof AffineTransform) { + return (AffineTransform) gridToCRS; + } + } + } + return null; + } + + /** + * Returns the scale of the "grid to CRS" transform, or {@link Double#NaN} if unknown. + * + * @param coverage The coverage for which to get the "grid to CRS" scale, or {@code null}. + * @return The "grid to CRS" scale, or {@code NaN} if none or if the transform is not affine. + */ + static double getScale(final Coverage coverage) { + final AffineTransform gridToCRS = getAffineTransform(coverage); + return (gridToCRS != null) ? XAffineTransform.getScale(gridToCRS) : Double.NaN; + } + } diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationLinear.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationLinear.xml new file mode 100644 index 00000000000..22c671e96a0 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationLinear.xml @@ -0,0 +1,16 @@ + + + + + + + + wcs__BlueMarble + image/tiff + + diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationMixedDuplicated.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationMixedDuplicated.xml new file mode 100644 index 00000000000..14e8ecd5fc9 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationMixedDuplicated.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + wcs__BlueMarble + image/tiff + + diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationMixedSupported.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationMixedSupported.xml new file mode 100644 index 00000000000..81ce06e6291 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationMixedSupported.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + wcs__BlueMarble + image/tiff + + diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationMixedTime.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationMixedTime.xml new file mode 100644 index 00000000000..e119e2ca390 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationMixedTime.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + wcs__BlueMarble + image/tiff + + diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationMixedUnsupported.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationMixedUnsupported.xml new file mode 100644 index 00000000000..caef31f3bbd --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationMixedUnsupported.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + wcs__BlueMarble + image/tiff + + diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationNearest.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationNearest.xml new file mode 100644 index 00000000000..0351307eeac --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageInterpolationNearest.xml @@ -0,0 +1,16 @@ + + + + + + + + wcs__BlueMarble + image/tiff + + diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageOutputCRS.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageOutputCRS.xml new file mode 100644 index 00000000000..c3cdca9759d --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageOutputCRS.xml @@ -0,0 +1,15 @@ + + + + http://www.opengis.net/def/crs/EPSG/0/3857 + + wcs__BlueMarble + image/tiff + diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageRangeSubsetting.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageRangeSubsetting.xml new file mode 100644 index 00000000000..03bb0e92158 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageRangeSubsetting.xml @@ -0,0 +1,21 @@ + + + wcs__BlueMarble + + + + band1 + + + + band3 + band5 + + + + + diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageScaleAxesByFactor.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageScaleAxesByFactor.xml new file mode 100644 index 00000000000..50bb51af5e3 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageScaleAxesByFactor.xml @@ -0,0 +1,27 @@ + + + + + + + + http://www.opengis.net/def/axis/OGC/1/i + + 3.5 + + + + http://www.opengis.net/def/axis/OGC/1/j + + 3.5 + + + + + wcs__BlueMarble + image/tiff + diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageScaleByFactor.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageScaleByFactor.xml new file mode 100644 index 00000000000..506701cbae6 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageScaleByFactor.xml @@ -0,0 +1,16 @@ + + + + + + 2.5 + + + + wcs__BlueMarble + image/tiff + diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageScaleToExtent.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageScaleToExtent.xml new file mode 100644 index 00000000000..3aa9b0e5382 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageScaleToExtent.xml @@ -0,0 +1,30 @@ + + + + + + + + http://www.opengis.net/def/axis/OGC/1/i + + 10 + 209 + + + + http://www.opengis.net/def/axis/OGC/1/j + + 20 + 319 + + + + + wcs__BlueMarble + image/tiff + + diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageScaleToSize.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageScaleToSize.xml new file mode 100644 index 00000000000..862ca3da127 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageScaleToSize.xml @@ -0,0 +1,28 @@ + + + + + + + + http://www.opengis.net/def/axis/OGC/1/i + + 1000 + + + + http://www.opengis.net/def/axis/OGC/1/j + + 1000 + + + + + wcs__BlueMarble + image/tiff + + diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageSlicingLatitudeNativeCRSXML.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageSlicingLatitudeNativeCRSXML.xml new file mode 100644 index 00000000000..9d73fd89a88 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageSlicingLatitudeNativeCRSXML.xml @@ -0,0 +1,16 @@ + + + wcs__BlueMarble + + Lat + -43.5 + + image/tiff + \ No newline at end of file diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageSlicingLongitudeNativeCRSXML.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageSlicingLongitudeNativeCRSXML.xml new file mode 100644 index 00000000000..4dfad7cbb90 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageSlicingLongitudeNativeCRSXML.xml @@ -0,0 +1,16 @@ + + + wcs__BlueMarble + + Long + 146.5 + + image/tiff + \ No newline at end of file diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageSubsettingCRS.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageSubsettingCRS.xml new file mode 100644 index 00000000000..9eabb299330 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageSubsettingCRS.xml @@ -0,0 +1,26 @@ + + + + Long + 146.5 + 147.0 + + + Lat + -43.5 + -43.0 + + + http://www.opengis.net/def/crs/EPSG/0/4326 + http://www.opengis.net/def/crs/EPSG/0/3857 + + wcs__BlueMarble + image/tiff + diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageSubsettingTrimCRS.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageSubsettingTrimCRS.xml new file mode 100644 index 00000000000..523ff3b2141 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageSubsettingTrimCRS.xml @@ -0,0 +1,25 @@ + + + + http://www.opengis.net/def/crs/EPSG/0/3857 + + http://www.opengis.net/def/crs/EPSG/0/3857 + + + wcs__BlueMarble + + X + 1.6308305401213994E7 + 1.6475284637403902E7 + + + Y + -5543147.203861462 + -5311971.846945147 + + image/tiff + diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageTrimmingDuplicatedNativeCRSXML.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageTrimmingDuplicatedNativeCRSXML.xml new file mode 100644 index 00000000000..583289d4623 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageTrimmingDuplicatedNativeCRSXML.xml @@ -0,0 +1,21 @@ + + + wcs__BlueMarble + + Long + -43.5 + + + Long + 146.5 + 147.0 + + image/tiff + \ No newline at end of file diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageTrimmingLatitudeNativeCRSXML.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageTrimmingLatitudeNativeCRSXML.xml new file mode 100644 index 00000000000..b27f588d272 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageTrimmingLatitudeNativeCRSXML.xml @@ -0,0 +1,17 @@ + + + wcs__BlueMarble + + Lat + -43.5 + -43.0 + + image/tiff + \ No newline at end of file diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageTrimmingLongNativeCRSXML.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageTrimmingLongNativeCRSXML.xml new file mode 100644 index 00000000000..1f163a87749 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageTrimmingLongNativeCRSXML.xml @@ -0,0 +1,17 @@ + + + wcs__BlueMarble + + Long + 146.5 + 147.0 + + image/tiff + \ No newline at end of file diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageTrimmingNativeCRSXML.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageTrimmingNativeCRSXML.xml new file mode 100644 index 00000000000..f770f633338 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageTrimmingNativeCRSXML.xml @@ -0,0 +1,24 @@ + + + wcs__BlueMarble + + Long + 146.5 + 147.0 + + + Lat + -43.5 + -43.0 + + image/tiff + \ No newline at end of file diff --git a/src/community/wcs2_0/src/test/resources/requestGetCoverageTrimmingSlicingNativeCRSXML.xml b/src/community/wcs2_0/src/test/resources/requestGetCoverageTrimmingSlicingNativeCRSXML.xml new file mode 100644 index 00000000000..4198a419230 --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetCoverageTrimmingSlicingNativeCRSXML.xml @@ -0,0 +1,21 @@ + + + wcs__BlueMarble + + Lat + -43.5 + + + Long + 146.5 + 147.0 + + image/tiff + \ No newline at end of file diff --git a/src/community/wcs2_0/src/test/resources/requestGetFullCoverage.xml b/src/community/wcs2_0/src/test/resources/requestGetFullCoverage.xml new file mode 100644 index 00000000000..3bb31fb4d8f --- /dev/null +++ b/src/community/wcs2_0/src/test/resources/requestGetFullCoverage.xml @@ -0,0 +1,10 @@ + + + wcs__BlueMarble + image/tiff + diff --git a/src/platform/src/main/java/org/geoserver/platform/OWS20Exception.java b/src/platform/src/main/java/org/geoserver/platform/OWS20Exception.java index cc119be075d..67e0e6b66f2 100644 --- a/src/platform/src/main/java/org/geoserver/platform/OWS20Exception.java +++ b/src/platform/src/main/java/org/geoserver/platform/OWS20Exception.java @@ -7,7 +7,6 @@ import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; -import org.geotools.util.logging.Logging; /** * Class for exceptions generated by an OWS2.0 service. @@ -35,7 +34,8 @@ public static class OWSExceptionCode { public final static OWSExceptionCode VersionNegotiationFailed = new OWSExceptionCode("VersionNegotiationFailed", 400, "Bad request"); public final static OWSExceptionCode InvalidUpdateSequence = new OWSExceptionCode("InvalidUpdateSequence", 400, "Bad request"); public final static OWSExceptionCode OptionNotSupported = new OWSExceptionCode("OptionNotSupported", 501, "Not Implemented"); - public final static OWSExceptionCode NoApplicableCode = new OWSExceptionCode("NoApplicableCode", 500, "Not Implemented"); + public final static OWSExceptionCode NoApplicableCode = new OWSExceptionCode("NoApplicableCode", 500, "Not Implemented"); + public final static OWSExceptionCode InvalidCoverageType = new OWSExceptionCode("InvalidCoverageType", 404, "Coverage addressed is not a grid coverage"); private final String exceptionCode; private final Integer httpCode;