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,

Reply via email to