Skip to content

Commit

Permalink
GEOS-8893] WCS 2.0 subsetting may return smaller images than expected…
Browse files Browse the repository at this point in the history
… by the standard
  • Loading branch information
aaime committed Aug 14, 2018
1 parent e7a65d2 commit 2c67d24
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 6 deletions.
44 changes: 44 additions & 0 deletions src/wcs/src/main/java/org/vfny/geoserver/util/WCSUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.filter.Filter;
import org.opengis.geometry.Envelope;
import org.opengis.metadata.spatial.PixelOrientation;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.vfny.geoserver.wcs.WcsException;

/**
Expand Down Expand Up @@ -125,6 +127,48 @@ public static GridCoverage2D crop(final GridCoverage2D coverage, final Envelope
return (GridCoverage2D) PROCESSOR.doOperation(param);
}

/**
* Pads the coverage to the specified bounds
*
* @param coverage The coverage to be padded
* @param bounds The bounds to pad to
* @return The padded covearge, or the original one, if the padding would not add a single pixel
* to it
*/
public static GridCoverage2D padToEnvelope(final GridCoverage2D coverage, final Envelope bounds)
throws TransformException {
GridGeometry2D gg = coverage.getGridGeometry();
GeneralEnvelope padRange =
CRS.transform(gg.getCRSToGrid2D(PixelOrientation.UPPER_LEFT), bounds);
GridEnvelope2D targetRange =
new GridEnvelope2D(
(int) Math.round(padRange.getMinimum(0)),
(int) Math.round(padRange.getMinimum(1)),
(int) Math.round(padRange.getMinimum(0) + padRange.getSpan(0)),
(int) Math.round(padRange.getMinimum(1) + padRange.getSpan(1)));
GridEnvelope2D sourceRange = gg.getGridRange2D();
if (sourceRange.x == targetRange.x
&& sourceRange.y == sourceRange.y
&& sourceRange.width == targetRange.width
&& sourceRange.height == targetRange.height) {
return coverage;
}

GridGeometry2D target =
new GridGeometry2D(
targetRange, gg.getGridToCRS(), gg.getCoordinateReferenceSystem2D());

List<GridCoverage2D> sources = new ArrayList<GridCoverage2D>(2);
sources.add(coverage);

// perform the mosaic
final ParameterValueGroup param = PROCESSOR.getOperation("Mosaic").getParameters();
param.parameter("Sources").setValue(sources);
param.parameter("geometry").setValue(target);

return (GridCoverage2D) PROCESSOR.doOperation(param);
}

/**
* <strong>Interpolating</strong><br>
* Specifies the interpolation type to be used to interpolate values for points which fall
Expand Down
59 changes: 53 additions & 6 deletions src/wcs2_0/src/main/java/org/geoserver/wcs2_0/GetCoverage.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
*/
package org.geoserver.wcs2_0;

import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.SampleModel;
import java.io.IOException;
Expand Down Expand Up @@ -871,6 +870,10 @@ private List<GridCoverage2D> readCoverage(

List<GridCoverage2D> readCoverages = new ArrayList<>();
for (GeneralEnvelope readEnvelope : readEnvelopes) {
// according to spec we need to return pixel in the intersection between
// the requested area and the declared bounds, readers might return less
GeneralEnvelope padEnvelope = computePadEnvelope(readEnvelope, reader);

// check if a previous read already covered this envelope, readers
// can return more than we asked
GridCoverage2D cov = null;
Expand Down Expand Up @@ -901,20 +904,53 @@ private List<GridCoverage2D> readCoverage(
}
readCoverages.add(cov);
}
// do we have more than requested?
Envelope2D covEnvelope = cov.getEnvelope2D();
GridCoverage2D cropped = cov;
if (covEnvelope.contains(readBoundingBox)
&& (covEnvelope.getWidth() > readBoundingBox.getWidth()
|| covEnvelope.getHeight() > readBoundingBox.getHeight())) {
GridCoverage2D cropped = cropOnEnvelope(cov, readEnvelope);
result.add(cropped);
} else {
result.add(cov);
cropped = cropOnEnvelope(cov, readEnvelope);
}

// do we have less than expected?
GridCoverage2D padded = cropped;
Envelope croppedEnvelope = cropped.getEnvelope();
if (!new GeneralEnvelope(croppedEnvelope).contains(padEnvelope, true)) {
padded = padOnEnvelope(cropped, padEnvelope);
}

result.add(padded);
}

return result;
}

/**
* Computes the envelope that GetCoveage should be returning given a reading envelope and the
* reader own native envelope (which is also the envelope we are declaring in output)
*/
private GeneralEnvelope computePadEnvelope(
GeneralEnvelope readEnvelope, GridCoverage2DReader reader) {
CoordinateReferenceSystem sourceCRS = reader.getCoordinateReferenceSystem();
CoordinateReferenceSystem subsettingCRS = readEnvelope.getCoordinateReferenceSystem();
try {
if (!CRS.equalsIgnoreMetadata(subsettingCRS, sourceCRS)) {
readEnvelope = CRS.transform(readEnvelope, sourceCRS);
}
} catch (TransformException e) {
throw new WCS20Exception(
"Unable to initialize subsetting envelope",
WCS20Exception.WCS20ExceptionCode.SubsettingCrsNotSupported,
subsettingCRS.toWKT(),
e);
}
GeneralEnvelope padEnvelope = new GeneralEnvelope(readEnvelope);
padEnvelope.intersect(reader.getOriginalEnvelope());

return padEnvelope;
}

private void addEnvelopes(
Envelope envelope,
List<GeneralEnvelope> readEnvelopes,
Expand Down Expand Up @@ -1693,6 +1729,17 @@ private GridCoverage2D cropOnEnvelope(GridCoverage2D coverage, Envelope cropEnve
return cropped;
}

private GridCoverage2D padOnEnvelope(GridCoverage2D coverage, GeneralEnvelope padEnvelope)
throws TransformException {
GridCoverage2D padded = WCSUtils.padToEnvelope(coverage, padEnvelope);
// in case of no padding just return the original coverage without wrapping
if (padded == coverage) {
return coverage;
}
padded = GridCoverageWrapper.wrapCoverage(padded, coverage, null, null, false);
return padded;
}

/**
* This method is responsible for handling the scaling WCS extension.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,81 @@ public void testCoverageTrimmingDuplicatedNativeCRSXML() throws Exception {

}

@Test
public void testCoverageTrimmingBordersOverlap() throws Exception {
final File xml =
new File(
"./src/test/resources/trimming/requestGetCoverageTrimmingBordersOverlap.xml");
testCoverageResult(
xml,
targetCoverage -> {
final GeneralEnvelope expectedEnvelope =
new GeneralEnvelope(new double[] {7, 40}, new double[] {11, 43});
expectedEnvelope.setCoordinateReferenceSystem(CRS.decode("EPSG:4326", true));
double pixelSize = 0.057934032977228;
// check the whole extent has been returned
assertTrue(
expectedEnvelope.equals(
targetCoverage.getEnvelope(), pixelSize, false));
});
}

@Test
public void testCoverageTrimmingBordersOverlapOutside() throws Exception {
final File xml =
new File(
"./src/test/resources/trimming/requestGetCoverageTrimmingBordersOverlapOutside.xml");
testCoverageResult(
xml,
targetCoverage -> {
// the expected envelope is the intersection between the requested and native
// one
final GeneralEnvelope expectedEnvelope =
new GeneralEnvelope(new double[] {6.344, 40}, new double[] {11, 46.59});
expectedEnvelope.setCoordinateReferenceSystem(CRS.decode("EPSG:4326", true));
double pixelSize = 0.057934032977228;
// check the whole extent has been returned
assertTrue(
expectedEnvelope.equals(
targetCoverage.getEnvelope(), pixelSize, false));
});
}

@FunctionalInterface
public interface GridTester {
void test(GridCoverage2D coverage) throws Exception;
}

void testCoverageResult(File xml, GridTester tester) throws Exception {
final String request = FileUtils.readFileToString(xml, "UTF-8");
MockHttpServletResponse response = postAsServletResponse("wcs", request);

assertEquals("image/tiff", response.getContentType());
byte[] tiffContents = getBinary(response);
File file =
File.createTempFile("borderOverlap", "borderOverlap.tiff", new File("./target"));
FileUtils.writeByteArrayToFile(file, tiffContents);

GeoTiffReader readerTarget = new GeoTiffReader(file);
GridCoverage2D targetCoverage = null;
try {
targetCoverage = readerTarget.read(null);

tester.test(targetCoverage);
} finally {
try {
readerTarget.dispose();
} catch (Exception e) {
// TODO: handle exception
}
try {
scheduleForCleaning(targetCoverage);
} catch (Exception e) {
// TODO: handle exception
}
}
}

@Test
public void testCoverageSlicingLongitudeNativeCRSXML() throws Exception {
final File xml =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<wcs:GetCoverage xmlns:wcs="http://www.opengis.net/wcs/2.0"
xmlns:wcsgeotiff="http://www.opengis.net/wcs/geotiff/1.0"
xmlns:crs="http://www.opengis.net/wcs/crs/1.0"
xmlns:gml="http://www.opengis.net/gml/3.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wcs/2.0
http://schemas.opengis.net/wcs/2.0/wcsAll.xsd
http://www.opengis.net/wcs/geotiff/1.0
http://schemas.opengis.net/wcs/geotiff/1.0/wcsGeotiff.xsd"
service="WCS" version="2.0.1">
<wcs:CoverageId>sf__borders</wcs:CoverageId>
<wcs:DimensionTrim>
<wcs:Dimension>Long</wcs:Dimension>
<wcs:TrimLow>7</wcs:TrimLow>
<wcs:TrimHigh>11</wcs:TrimHigh>
</wcs:DimensionTrim>
<wcs:DimensionTrim>
<wcs:Dimension>Lat</wcs:Dimension>
<wcs:TrimLow>40</wcs:TrimLow>
<wcs:TrimHigh>43</wcs:TrimHigh>
</wcs:DimensionTrim>
<wcs:format>image/tiff</wcs:format>
</wcs:GetCoverage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<wcs:GetCoverage xmlns:wcs="http://www.opengis.net/wcs/2.0"
xmlns:wcsgeotiff="http://www.opengis.net/wcs/geotiff/1.0"
xmlns:crs="http://www.opengis.net/wcs/crs/1.0"
xmlns:gml="http://www.opengis.net/gml/3.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wcs/2.0
http://schemas.opengis.net/wcs/2.0/wcsAll.xsd
http://www.opengis.net/wcs/geotiff/1.0
http://schemas.opengis.net/wcs/geotiff/1.0/wcsGeotiff.xsd"
service="WCS" version="2.0.1">
<wcs:CoverageId>sf__borders</wcs:CoverageId>
<wcs:DimensionTrim>
<wcs:Dimension>Long</wcs:Dimension>
<wcs:TrimLow>5</wcs:TrimLow>
<wcs:TrimHigh>11</wcs:TrimHigh>
</wcs:DimensionTrim>
<wcs:DimensionTrim>
<wcs:Dimension>Lat</wcs:Dimension>
<wcs:TrimLow>40</wcs:TrimLow>
<wcs:TrimHigh>47</wcs:TrimHigh>
</wcs:DimensionTrim>
<wcs:format>image/tiff</wcs:format>
</wcs:GetCoverage>

0 comments on commit 2c67d24

Please sign in to comment.