This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new ea18458970 Refactor `CoverageCombiler` for accepting an array of
source coverages. Add more ASCII Grid tests, including a test using
(indirectly) `CoverageCombiner`.
ea18458970 is described below
commit ea18458970d1d22ae0709b96a44aec87470338c4
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sat Apr 9 16:56:20 2022 +0200
Refactor `CoverageCombiler` for accepting an array of source coverages.
Add more ASCII Grid tests, including a test using (indirectly)
`CoverageCombiner`.
---
.../sis/internal/coverage/CoverageCombiner.java | 268 ++++++++++-----------
.../apache/sis/internal/storage/PRJDataStore.java | 1 +
.../internal/storage/WritableResourceSupport.java | 2 +-
.../sis/internal/storage/ascii/StoreProvider.java | 2 +-
.../sis/internal/storage/ascii/StoreTest.java | 26 +-
.../internal/storage/ascii/WritableStoreTest.java | 146 +++++++++++
.../apache/sis/test/suite/StorageTestSuite.java | 1 +
7 files changed, 304 insertions(+), 142 deletions(-)
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/CoverageCombiner.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/CoverageCombiner.java
index d191b99a23..e8c4b09818 100644
---
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/CoverageCombiner.java
+++
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/CoverageCombiner.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.internal.coverage;
+import java.util.Arrays;
import java.awt.Dimension;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRenderedImage;
@@ -27,20 +28,15 @@ import org.apache.sis.coverage.grid.GridExtent;
import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.coverage.grid.GridCoverage;
import org.apache.sis.coverage.grid.ImageRenderer;
-import org.apache.sis.coverage.grid.DisjointExtentException;
import org.apache.sis.image.ImageProcessor;
import org.apache.sis.image.ImageCombiner;
+import org.apache.sis.image.Interpolation;
import org.apache.sis.image.PlanarImage;
+import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.resources.Errors;
-import static java.lang.Math.addExact;
-import static java.lang.Math.subtractExact;
-import static java.lang.Math.multiplyExact;
-import static java.lang.Math.toIntExact;
import static java.lang.Math.round;
-import static java.lang.Math.min;
-import static java.lang.Math.max;
import static org.apache.sis.internal.util.Numerics.saturatingAdd;
import static org.apache.sis.internal.util.Numerics.saturatingSubtract;
@@ -53,8 +49,7 @@ import static
org.apache.sis.internal.util.Numerics.saturatingSubtract;
* <ol>
* <li>Creates a {@code CoverageCombiner} with the destination coverage
where to write.</li>
* <li>Configure with methods such as {@link #setInterpolation
setInterpolation(…)}.</li>
- * <li>Invoke {@link #accept accept(…)} or {@link #resample resample(…)}
- * methods for each coverage to combine.</li>
+ * <li>Invoke {@link #apply apply(…)} methods for each list of coverages to
combine.</li>
* <li>Get the combined coverage with {@link #result()}.</li>
* </ol>
*
@@ -94,47 +89,25 @@ public final class CoverageCombiner {
*/
private final GridCoverage destination;
- /**
- * The image combiners to use for combining two-dimensional slices.
- * There is one {@link ImageCombiner} instance per slice, created when
first needed.
- */
- private final ImageCombiner[] sliceCombiners;
-
- /**
- * Number of dimensions of the destination grid coverage.
- */
- private final int dimension;
-
/**
* The dimension to extract as {@link RenderedImage}s.
* This is usually 0 for <var>x</var> and 1 for <var>y</var>.
*/
private final int xdim, ydim;
- /**
- * The offset to subtract to grid index before to compute the index of a
slice.
- */
- private final long[] sliceIndexOffsets;
-
- /**
- * The multiplication factor to apply of grid coordinates (after
subtracting the offset)
- * for computing slice coordinates.
- */
- private final int[] sliceIndexSpans;
-
/**
* Creates a coverage combiner which will write in the given coverage.
* The coverage is not cleared; cells that are not overwritten by calls
* to the {@code accept(…)} method will be left unchanged.
*
- * @param destination the coverage where to combine coverages.
+ * @param destination the destination coverage where to combine source
coverages.
* @param xdim the dimension to extract as {@link RenderedImage}
<var>x</var> axis. This is usually 0.
* @param ydim the dimension to extract as {@link RenderedImage}
<var>y</var> axis. This is usually 1.
*/
public CoverageCombiner(final GridCoverage destination, final int xdim,
final int ydim) {
+ ArgumentChecks.ensureNonNull("destination", destination);
this.destination = destination;
- final GridExtent extent = destination.getGridGeometry().getExtent();
- dimension = extent.getDimension();
+ final int dimension = destination.getGridGeometry().getDimension();
ArgumentChecks.ensureBetween("xdim", 0, dimension-1, xdim);
ArgumentChecks.ensureBetween("ydim", 0, dimension-1, ydim);
if (xdim == ydim) {
@@ -142,21 +115,35 @@ public final class CoverageCombiner {
}
this.xdim = xdim;
this.ydim = ydim;
- sliceIndexOffsets = new long[dimension - BIDIMENSIONAL];
- sliceIndexSpans = new int [dimension - BIDIMENSIONAL];
- int sliceCount = 1;
- for (int j=0,i=0; i<dimension; i++) {
- if (i != xdim && i != ydim) {
- final int span;
- sliceIndexOffsets[j] = extent.getLow(i);
- sliceIndexSpans[j++] = span = toIntExact(extent.getSize(i));
- sliceCount = multiplyExact(sliceCount, span);
- }
- }
- sliceCombiners = new ImageCombiner[sliceCount];
processor = new ImageProcessor();
}
+ /**
+ * Returns the interpolation method to use during resample operations.
+ *
+ * <h4>Limitations</h4>
+ * In current version, the interpolation is applied only in the {@code
xdim} and {@code ydim} dimensions
+ * specified at construction time. For all other dimensions, nearest
neighbor interpolation is applied.
+ *
+ * @return interpolation method to use during resample operations.
+ */
+ public Interpolation getInterpolation() {
+ return processor.getInterpolation();
+ }
+
+ /**
+ * Sets the interpolation method to use during resample operations.
+ *
+ * <h4>Limitations</h4>
+ * In current version, the interpolation is applied only in the {@code
xdim} and {@code ydim} dimensions
+ * specified at construction time. For all other dimensions, nearest
neighbor interpolation is applied.
+ *
+ * @param method interpolation method to use during resample operations.
+ */
+ public void setInterpolation(final Interpolation method) {
+ processor.setInterpolation(method);
+ }
+
/**
* Returns information about conversion from pixel coordinates to "real
world" coordinates.
* This is taken from {@link PlanarImage#GRID_GEOMETRY_KEY} if available,
or computed otherwise.
@@ -177,127 +164,140 @@ public final class CoverageCombiner {
}
/**
- * Writes the given coverage on top of destination coverage.
- * The given coverage is resampled to the grid geometry of the destination
coverage.
+ * Writes the given coverages on top of the destination coverage.
+ * The given coverages are resampled to the grid geometry of the
destination coverage.
+ * Coverages that do not intercept with the destination coverage are
silently ignored.
*
- * @param source the coverage to write on top of destination coverage.
+ * @param sources the coverages to write on top of destination coverage.
* @return {@code true} on success, or {@code false} if at least one slice
* in the destination coverage is not writable.
- * @throws TransformException if the coordinates of given coverage can not
be transformed
+ * @throws TransformException if the coordinates of a given coverage can
not be transformed
* to the coordinates of destination coverage.
*/
- public boolean accept(final GridCoverage source) throws TransformException
{
- final Dimension margin = processor.getInterpolation().getSupportSize();
- margin.width = ((margin.width + 1) >> 1) + 1;
- margin.height = ((margin.height + 1) >> 1) + 1;
- final long[] minIndices = new long[dimension]; // Will be
expanded by above margin.
- final long[] maxIndices = new long[dimension]; // Inclusive.
+ public boolean apply(GridCoverage... sources) throws TransformException {
+ ArgumentChecks.ensureNonNull("sources", sources);
+ sources = sources.clone();
+ final GridGeometry targetGG =
destination.getGridGeometry();
+ final GridExtent targetEx = targetGG.getExtent();
+ final int dimension = targetEx.getDimension();
+ final long[] minIndices = new long[dimension];
Arrays.fill(minIndices, Long.MAX_VALUE);
+ final long[] maxIndices = new long[dimension];
Arrays.fill(maxIndices, Long.MIN_VALUE);
+ final MathTransform[] toSourceSliceCorner = new
MathTransform[sources.length];
+ final MathTransform[] toSourceSliceCenter = new
MathTransform[sources.length];
/*
- * Compute the intersection between `source` and `destination`, in
units
- * of destination cell indices. A margin is added for interpolations.
- * This block also verifies that the intersection exists.
+ * Compute the intersection between `source` and `destination`, in
units of destination cell indices.
+ * If a coverage does not intersect the destination, the corresponding
element in the `sources` array
+ * will be set to null.
*/
- final double[] centerIndices;
- final double[] centerSourceIndices;
- final MathTransform toSourceSliceCorner;
- final MathTransform toSourceSliceCenter;
- { // For keeping following variables in a local scope.
- final GridGeometry targetGG = destination.getGridGeometry();
- final GridGeometry sourceGG = source.getGridGeometry();
- final GridExtent sourceEx = sourceGG.getExtent();
- final GridExtent targetEx = targetGG.getExtent();
- centerIndices = targetEx.getPointOfInterest();
- centerSourceIndices = new double[sourceEx.getDimension()];
- toSourceSliceCorner = targetGG.createTransformTo(sourceGG,
PixelInCell.CELL_CORNER);
- toSourceSliceCenter = targetGG.createTransformTo(sourceGG,
PixelInCell.CELL_CENTER);
- final Envelope env =
sourceEx.toEnvelope(toSourceSliceCorner.inverse());
+next: for (int j=0; j<sources.length; j++) {
+ final GridCoverage source = sources[j];
+ ArgumentChecks.ensureNonNullElement("sources", j, source);
+ final GridGeometry sourceGG = source.getGridGeometry();
+ final GridExtent sourceEx = sourceGG.getExtent();
+ final MathTransform toSource =
targetGG.createTransformTo(sourceGG, PixelInCell.CELL_CORNER);
+ final Envelope env =
sourceEx.toEnvelope(toSource.inverse());
+ final long[] min = new long[dimension];
+ final long[] max = new long[dimension];
for (int i=0; i<dimension; i++) {
- minIndices[i] = max(targetEx.getLow (i),
round(env.getMinimum(i)));
- maxIndices[i] = min(targetEx.getHigh(i),
round(env.getMaximum(i) - 1));
- if (minIndices[i] >= maxIndices[i]) {
- throw new DisjointExtentException();
+ /*
+ * The conversion from `double` to `long` may loose precision.
The -1 (for making the maximum value
+ * inclusive) is done on the floating point value instead of
the integer value in order to have the
+ * same rounding when the minimum and maximum values are close
to each other.
+ * The goal is to avoid spurious "disjoint extent" exceptions.
+ */
+ min[i] = Math.max(targetEx.getLow (i),
round(env.getMinimum(i)));
+ max[i] = Math.min(targetEx.getHigh(i), round(env.getMaximum(i)
- 1));
+ if (min[i] > max[i]) {
+ sources[j] = null;
+ continue next;
}
}
+ /*
+ * Expand the destination extent only if the source intersects it.
+ * It can be done only after we tested all dimensions.
+ */
+ for (int i=0; i<dimension; i++) {
+ minIndices[i] = Math.min(minIndices[i], min[i]);
+ maxIndices[i] = Math.max(maxIndices[i], max[i]);
+ }
+ toSourceSliceCenter[j] = targetGG.createTransformTo(sourceGG,
PixelInCell.CELL_CENTER);
+ toSourceSliceCorner[j] = toSource;
+ }
+ if (ArraysExt.allEquals(sources, null)) {
+ return true; // No intersection. We
"successfully" wrote nothing.
}
/*
* Now apply `ImageCombiner` for each two-dimensional slice. We will
iterate on all destination slices
* in the intersection area, and locate the corresponding source
slices (this is a strategy similar to
* the resampling of pixel values in rasters).
*/
- final long[] minSliceIndices = minIndices.clone();
- final long[] maxSliceIndices = maxIndices.clone();
- final long[] minSourceIndices = new long[centerSourceIndices.length];
- final long[] maxSourceIndices = new long[centerSourceIndices.length];
+ final long[] minSliceIndices = minIndices.clone();
+ final long[] maxSliceIndices = maxIndices.clone();
+ final double[] centerIndices = targetEx.getPointOfInterest();
+ final Dimension margin = processor.getInterpolation().getSupportSize();
+ margin.width = ((margin.width + 1) >> 1) + 1;
+ margin.height = ((margin.height + 1) >> 1) + 1;
boolean success = true;
next: for (;;) {
/*
- * Compute the index in `sliceCombiners` array for the
two-dimensional
- * slice identified by the current value of the `slice`
coordinates.
- */
- int sliceIndex = 0;
- for (int j=0,i=0; i<dimension; i++) {
- if (i != xdim && i != ydim) {
- maxSliceIndices[i] = minSliceIndices[i];
- int offset = toIntExact(subtractExact(minSliceIndices[i],
sliceIndexOffsets[j]));
- offset = multiplyExact(offset, sliceIndexSpans[j++]);
- sliceIndex = addExact(sliceIndex, offset);
- }
- }
- /*
- * Get the image for the current slice. It may be the result of a
previous combination.
+ * Get the image for the current slice to write.
* If the image is not writable, we skip that slice and try the
next one.
* A flag will report to the user that at least one slice was
non-writable.
*/
final GridExtent targetSliceExtent = new GridExtent(null,
minSliceIndices, maxSliceIndices, true);
- final RenderedImage targetSlice;
- ImageCombiner combiner = sliceCombiners[sliceIndex];
- if (combiner != null) {
- targetSlice = combiner.result();
- } else {
- targetSlice = destination.render(targetSliceExtent);
- if (targetSlice instanceof WritableRenderedImage) {
- combiner = new ImageCombiner((WritableRenderedImage)
targetSlice, processor);
- sliceCombiners[sliceIndex] = combiner;
- } else {
- success = false;
- }
- }
- /*
- * Compute the bounds of the source image to load (with a margin
for rounding and interpolations).
- * For all dimensions other than the slice dimensions, we take the
center of the slice to read.
- */
- if (combiner != null) {
- toSourceSliceCenter.transform(centerIndices, 0,
centerSourceIndices, 0, 1);
- final Envelope sourceArea =
targetSliceExtent.toEnvelope(toSourceSliceCorner);
- for (int i=0; i<minSourceIndices.length; i++) {
- if (i == xdim || i == ydim) {
- final int m = (i == xdim) ? margin.width :
margin.height;
- minSourceIndices[i] =
saturatingSubtract(round(sourceArea.getMinimum(i)), m );
- maxSourceIndices[i] = saturatingAdd
(round(sourceArea.getMaximum(i)), m-1);
- } else {
- minSourceIndices[i] = round(centerSourceIndices[i]);
- maxSourceIndices[i] = minSourceIndices[i];
+ final RenderedImage targetSlice =
destination.render(targetSliceExtent);
+ if (targetSlice instanceof WritableRenderedImage) {
+ final ImageCombiner combiner = new
ImageCombiner((WritableRenderedImage) targetSlice, processor);
+ for (int j=0; j<sources.length; j++) {
+ final GridCoverage source = sources[j];
+ if (source == null) {
+ continue;
+ }
+ /*
+ * Compute the bounds of the source image to load (with a
margin for rounding and interpolations).
+ * For all dimensions other than the slice dimensions, we
take the center of the slice to read.
+ */
+ final int srcDim =
source.getGridGeometry().getDimension();
+ final long[] minSourceIndices = new long [srcDim];
+ final long[] maxSourceIndices = new long [srcDim];
+ final double[] centerSourceIndices = new double[srcDim];
+ toSourceSliceCenter[j].transform(centerIndices, 0,
centerSourceIndices, 0, 1);
+ final Envelope env =
targetSliceExtent.toEnvelope(toSourceSliceCorner[j]);
+ for (int i=0; i<srcDim; i++) {
+ if (i == xdim || i == ydim) {
+ final int m = (i == xdim) ? margin.width :
margin.height;
+ minSourceIndices[i] =
saturatingSubtract(round(env.getMinimum(i)), m );
+ maxSourceIndices[i] = saturatingAdd
(round(env.getMaximum(i)), m-1);
+ } else {
+ minSourceIndices[i] =
round(centerSourceIndices[i]);
+ maxSourceIndices[i] = minSourceIndices[i];
+ }
}
+ /*
+ * Get the source image and combine with the corresponding
slice of destination coverage.
+ */
+ GridExtent sourceSliceExtent = new GridExtent(null,
minSourceIndices, maxSourceIndices, true);
+ RenderedImage sourceSlice =
source.render(sourceSliceExtent);
+ MathTransform toSource =
+ getGridGeometry(targetSlice, destination,
targetSliceExtent).createTransformTo(
+ getGridGeometry(sourceSlice, source,
sourceSliceExtent), PixelInCell.CELL_CENTER);
+ combiner.resample(sourceSlice, null, toSource);
}
- GridExtent sourceSliceExtent = new GridExtent(null,
minSourceIndices, maxSourceIndices, true);
- /*
- * Get the source image and combine with the corresponding
slice of destination coverage.
- */
- RenderedImage sourceSlice = source.render(sourceSliceExtent);
- MathTransform toSource =
- getGridGeometry(targetSlice, destination,
targetSliceExtent).createTransformTo(
- getGridGeometry(sourceSlice, source,
sourceSliceExtent), PixelInCell.CELL_CENTER);
- combiner.resample(sourceSlice, null, toSource);
+ } else {
+ success = false;
}
/*
* Increment indices to the next slice.
*/
for (int i=0; i<dimension; i++) {
if (i != xdim && i != ydim) {
- if (minSliceIndices[i]++ <= maxIndices[i]) {
+ long index = minSliceIndices[i];
+ boolean done = index++ <= maxIndices[i];
+ if (!done) index = minIndices[i];
+ maxSliceIndices[i] = minSliceIndices[i] = index;
+ if (done) {
continue next;
}
- minSliceIndices[i] = minIndices[i];
}
}
break;
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/PRJDataStore.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/PRJDataStore.java
index a55a8209ab..c624b16ff4 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/PRJDataStore.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/PRJDataStore.java
@@ -215,6 +215,7 @@ public abstract class PRJDataStore extends URIDataStore {
final StoreFormat format = new StoreFormat(locale, timezone,
null, listeners);
format.setConvention(Convention.WKT1_COMMON_UNITS);
format.format(crs, out);
+ out.write(System.lineSeparator());
}
} catch (IOException e) {
Object identifier = getIdentifier().orElse(null);
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/WritableResourceSupport.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/WritableResourceSupport.java
index e41d83c960..2fd5c99537 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/WritableResourceSupport.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/WritableResourceSupport.java
@@ -169,7 +169,7 @@ public final class WritableResourceSupport implements
Localized {
final GridCoverage existing = resource.read(null, null);
final CoverageCombiner combiner = new CoverageCombiner(existing, 0, 1);
try {
- if (!combiner.accept(coverage)) {
+ if (!combiner.apply(coverage)) {
throw new ReadOnlyStorageException(canNotWrite());
}
} catch (TransformException e) {
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/StoreProvider.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/StoreProvider.java
index feb70066ca..aa58acc499 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/StoreProvider.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/StoreProvider.java
@@ -44,7 +44,7 @@ import org.apache.sis.internal.storage.PRJDataStore;
*/
@StoreMetadata(formatName = StoreProvider.NAME,
fileSuffixes = {"asc", "grd", "agr", "aig"},
- capabilities = Capability.READ,
+ capabilities = {Capability.READ, Capability.WRITE,
Capability.CREATE},
resourceTypes = GridCoverageResource.class)
public final class StoreProvider extends PRJDataStore.Provider {
/**
diff --git
a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ascii/StoreTest.java
b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ascii/StoreTest.java
index 2f8804f188..3743611b11 100644
---
a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ascii/StoreTest.java
+++
b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ascii/StoreTest.java
@@ -26,6 +26,7 @@ import org.apache.sis.coverage.Category;
import org.apache.sis.coverage.grid.GridCoverage;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.storage.ProbeResult;
import org.apache.sis.test.TestCase;
import org.junit.Test;
@@ -34,7 +35,7 @@ import static org.apache.sis.test.TestUtilities.getSingleton;
/**
- * Tests {@link Store}.
+ * Tests {@link Store} and {@link StoreProvider}.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.2
@@ -43,10 +44,23 @@ import static
org.apache.sis.test.TestUtilities.getSingleton;
*/
public final strictfp class StoreTest extends TestCase {
/**
- * Opens an ASCII Grid store on the test data.
+ * Returns a storage connector with the URL to the test data.
*/
- private static Store open() throws DataStoreException {
- return new Store(null, new
StorageConnector(StoreTest.class.getResource("grid.asc")));
+ private static StorageConnector testData() {
+ return new StorageConnector(StoreTest.class.getResource("grid.asc"));
+ }
+
+ /**
+ * Tests {@link StoreProvider#probeContent(StorageConnector)} method.
+ *
+ * @throws DataStoreException if en error occurred while reading the CSV
file.
+ */
+ @Test
+ public void testProbeContent() throws DataStoreException {
+ final StoreProvider p = new StoreProvider();
+ final ProbeResult r = p.probeContent(testData());
+ assertTrue(r.isSupported());
+ assertEquals("text/plain", r.getMimeType());
}
/**
@@ -58,7 +72,7 @@ public final strictfp class StoreTest extends TestCase {
*/
@Test
public void testMetadata() throws DataStoreException {
- try (Store store = open()) {
+ try (Store store = new Store(null, testData())) {
assertEquals("grid", store.getIdentifier().get().toString());
final Metadata metadata = store.getMetadata();
/*
@@ -91,7 +105,7 @@ public final strictfp class StoreTest extends TestCase {
*/
@Test
public void testRead() throws DataStoreException {
- try (Store store = open()) {
+ try (Store store = new Store(null, testData())) {
final List<Category> categories =
getSingleton(store.getSampleDimensions()).getCategories();
assertEquals(2, categories.size());
assertEquals( -2,
categories.get(0).getSampleRange().getMinDouble(), 1);
diff --git
a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ascii/WritableStoreTest.java
b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ascii/WritableStoreTest.java
new file mode 100644
index 0000000000..c36ff2a82f
--- /dev/null
+++
b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ascii/WritableStoreTest.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.storage.ascii;
+
+import java.nio.file.Path;
+import java.nio.file.Files;
+import java.io.IOException;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferByte;
+import java.nio.file.StandardOpenOption;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.coverage.grid.GridCoverageBuilder;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.geometry.Envelope2D;
+import org.apache.sis.setup.OptionKey;
+import org.apache.sis.referencing.crs.HardCodedCRS;
+import org.apache.sis.storage.ResourceAlreadyExistsException;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests {@link WritableStore}.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since 1.2
+ * @module
+ */
+public final strictfp class WritableStoreTest extends TestCase {
+ /**
+ * Creates a test grid coverage filled with arbitrary data.
+ *
+ * @param crs the CRS to assign to the coverage. May be {@code null}.
+ */
+ private static GridCoverage createTestCoverage(final
CoordinateReferenceSystem crs) {
+ final BufferedImage image = new BufferedImage(4, 3,
BufferedImage.TYPE_BYTE_GRAY);
+ final byte[] data = ((DataBufferByte)
image.getRaster().getDataBuffer()).getData();
+ for (int i=0; i<data.length; i++) {
+ data[i] = (byte) ((i+2) * 3);
+ }
+ return new GridCoverageBuilder().setValues(image)
+ .setDomain(new Envelope2D(crs, 20, 10, 8, 9))
+ .flipGridAxis(1).build();
+ }
+
+ /**
+ * Verifies that the content of the given file is equal to the expected
values
+ * for a coverage created by {@link
#createTestCoverage(CoordinateReferenceSystem)}.
+ */
+ private static void verifyContent(final Path file) throws IOException {
+ assertArrayEquals(new String[] {
+ "NCOLS 4",
+ "NROWS 3",
+ "XLLCORNER 20.0",
+ "YLLCORNER 10.0",
+ "XCELLSIZE 2.0",
+ "YCELLSIZE 3.0",
+ "NODATA_VALUE NaN",
+ "6 9 12 15",
+ "18 21 24 27",
+ "30 33 36 39",
+ }, Files.readAllLines(file).toArray());
+ assertArrayEquals(new String[] {
+ "GEOGCS[\"WGS 84\",",
+ " DATUM[\"World Geodetic System 1984\",",
+ " SPHEROID[\"WGS84\", 6378137.0, 298.257223563]],",
+ " PRIMEM[\"Greenwich\", 0.0],",
+ " UNIT[\"degree\", 0.017453292519943295],",
+ " AXIS[\"Longitude\", EAST],",
+ " AXIS[\"Latitude\", NORTH]]",
+ }, Files.readAllLines(toPRJ(file)).toArray());
+ }
+
+ /**
+ * Given the path to an ASCII Grid file, returns the path to its PRJ
auxiliary file.
+ */
+ private static Path toPRJ(final Path file) {
+ String filename = file.getFileName().toString();
+ filename = filename.substring(0, filename.lastIndexOf('.')) + ".prj";
+ return file.resolveSibling(filename);
+ }
+
+ /**
+ * Tests writing an ASCII Grid in a temporary file.
+ *
+ * @throws IOException if the temporary file can not be created.
+ * @throws DataStoreException if an error occurred while reading the file.
+ */
+ @Test
+ public void testWriteFile() throws IOException, DataStoreException {
+ final GridCoverage coverage = createTestCoverage(HardCodedCRS.WGS84);
+ final Path file = Files.createTempFile(null, ".asc");
+ try {
+ final Path filePRJ = toPRJ(file);
+ final StorageConnector sc = new StorageConnector(file);
+ sc.setOption(OptionKey.OPEN_OPTIONS, new StandardOpenOption[]
{StandardOpenOption.WRITE});
+ boolean deleted = false;
+ try (WritableStore store = new WritableStore(null, sc)) {
+ store.write(coverage);
+ verifyContent(file);
+ /*
+ * Verify that a second attempt to write the coverage fails,
+ * because now a coverage already exists.
+ */
+ try {
+ store.write(coverage);
+ fail("Should not be allowed to overwrite an existing
coverage.");
+ } catch (ResourceAlreadyExistsException e) {
+ assertNotNull(e.getMessage());
+ }
+ verifyContent(file);
+ /*
+ * Write again, this time allowing the writer to replace the
existing image.
+ */
+ store.write(coverage, WritableStore.CommonOption.REPLACE);
+ verifyContent(file);
+ store.write(coverage, WritableStore.CommonOption.UPDATE);
+ verifyContent(file);
+ } finally {
+ deleted = Files.deleteIfExists(filePRJ);
+ }
+ assertTrue("Missing PRJ file.", deleted);
+ } finally {
+ Files.delete(file);
+ }
+ }
+}
diff --git
a/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
b/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
index 222e1dd34b..72f51311fe 100644
---
a/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
+++
b/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
@@ -58,6 +58,7 @@ import org.junit.BeforeClass;
org.apache.sis.internal.storage.csv.StoreProviderTest.class,
org.apache.sis.internal.storage.csv.StoreTest.class,
org.apache.sis.internal.storage.ascii.StoreTest.class,
+ org.apache.sis.internal.storage.ascii.WritableStoreTest.class,
org.apache.sis.internal.storage.folder.StoreTest.class,
org.apache.sis.internal.storage.JoinFeatureSetTest.class,
org.apache.sis.internal.storage.ConcatenatedFeatureSetTest.class,