You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2023/10/30 21:45:11 UTC

(sis) branch geoapi-4.0 updated (0de784f0e4 -> 313bfa0ac1)

This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


    from 0de784f0e4 Reorganize import statements with a better separation of test dependencies.
     new f7dabc89f1 Replace `api` dependency to Glassfish by a pair of `compileOnly` and `runtimeOnly`. The intend is to avoid that this dependency appears in Maven pom files.
     new b12a8b466a Add `ImageOutputStream` support in `StorageConnector`.
     new 313bfa0ac1 Add compression support for GeoTIFF. Only "Deflate" is supported for now.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 endorsed/build.gradle.kts                          |   3 +-
 .../main/module-info.java                          |   2 +-
 .../apache/sis/storage/geotiff/Compression.java    | 155 +++++++++++++++++++++
 .../apache/sis/storage/geotiff/GeoTiffStore.java   |  17 ++-
 .../sis/storage/geotiff/GeoTiffStoreProvider.java  |  12 +-
 .../org/apache/sis/storage/geotiff/Writer.java     |  18 ++-
 .../sis/storage/geotiff/base/Compression.java      |   2 +-
 .../storage/geotiff/writer/CompressionChannel.java |  84 +++++++++++
 .../sis/storage/geotiff/writer/TileMatrix.java     | 102 ++++++++++----
 .../org/apache/sis/storage/geotiff/writer/ZIP.java | 102 ++++++++++++++
 .../org/apache/sis/storage/geotiff/WriterTest.java |   1 +
 .../apache/sis/io/stream/ChannelDataOutput.java    |  10 ++
 .../sis/io/stream/ChannelImageOutputStream.java    |  14 ++
 .../org/apache/sis/storage/StorageConnector.java   | 109 ++++++++++-----
 .../org/apache/sis/util/resources/Vocabulary.java  |   5 +
 .../sis/util/resources/Vocabulary.properties       |   1 +
 .../sis/util/resources/Vocabulary_fr.properties    |   1 +
 17 files changed, 567 insertions(+), 71 deletions(-)
 create mode 100644 endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Compression.java
 create mode 100644 endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/CompressionChannel.java
 create mode 100644 endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/ZIP.java


(sis) 02/03: Add `ImageOutputStream` support in `StorageConnector`.

Posted by de...@apache.org.
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

commit b12a8b466af74f71e787357a2adf40160706f195
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Mon Oct 30 12:21:13 2023 +0100

    Add `ImageOutputStream` support in `StorageConnector`.
---
 .../sis/io/stream/ChannelImageOutputStream.java    |  14 +++
 .../org/apache/sis/storage/StorageConnector.java   | 109 ++++++++++++++-------
 2 files changed, 90 insertions(+), 33 deletions(-)

diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelImageOutputStream.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelImageOutputStream.java
index 46f8afe93c..d6f9de7347 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelImageOutputStream.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelImageOutputStream.java
@@ -21,6 +21,7 @@ import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.channels.ByteChannel;
+import java.nio.channels.ReadableByteChannel;
 import javax.imageio.stream.IIOByteBuffer;
 import javax.imageio.stream.ImageOutputStream;
 
@@ -68,6 +69,19 @@ public class ChannelImageOutputStream extends OutputStream implements ImageOutpu
         output = new ChannelDataOutput(filename, channel, buffer);
     }
 
+    /**
+     * Creates a new input/output stream wrapping the given output data channel.
+     *
+     * @param  output  the object to use for writing to the channel.
+     * @throws ClassCastException if the output channel is not readable.
+     * @throws IOException if the stream cannot be created.
+     */
+    public ChannelImageOutputStream(final ChannelDataOutput output) throws IOException {
+        this.output = output;
+        input = new ChannelImageInputStream(output.filename, (ReadableByteChannel) output.channel, output.buffer, true);
+        writing = true;
+    }
+
     /**
      * Returns the {@linkplain #input} or {@linkplain #output},
      * depending on whether this stream is reading or writing.
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/StorageConnector.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/StorageConnector.java
index 814d664963..a2bf92f40a 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/StorageConnector.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/StorageConnector.java
@@ -67,6 +67,7 @@ import org.apache.sis.io.stream.ChannelData;
 import org.apache.sis.io.stream.ChannelDataInput;
 import org.apache.sis.io.stream.ChannelDataOutput;
 import org.apache.sis.io.stream.ChannelImageInputStream;
+import org.apache.sis.io.stream.ChannelImageOutputStream;
 import org.apache.sis.io.stream.InputStreamAdapter;
 import org.apache.sis.io.stream.RewindableLineReader;
 import org.apache.sis.io.stream.InternalOptionKey;
@@ -208,6 +209,7 @@ public class StorageConnector implements Serializable {
         add(DataInput.class,         StorageConnector::createDataInput);
         add(DataOutput.class,        StorageConnector::createDataOutput);
         add(ImageInputStream.class,  StorageConnector::createImageInputStream);
+        add(ImageOutputStream.class, StorageConnector::createImageOutputStream);
         add(InputStream.class,       StorageConnector::createInputStream);
         add(OutputStream.class,      StorageConnector::createOutputStream);
         add(Reader.class,            StorageConnector::createReader);
@@ -389,7 +391,6 @@ public class StorageConnector implements Serializable {
          * @param  cascade     bitwise combination of {@link #CASCADE_ON_CLOSE}, {@link #CASCADE_ON_RESET}
          *                     or {@link #CLEAR_ON_RESET}.
          */
-        @SuppressWarnings("ThisEscapedInObjectConstruction")
         Coupled(final Coupled wrapperFor, final byte cascade) {
             this.wrapperFor = wrapperFor;
             this.cascade    = cascade;
@@ -521,6 +522,8 @@ public class StorageConnector implements Serializable {
                  * which should cause the block below (with a call to `rewind()`) to be executed.
                  */
                 ((ChannelData) view).seek(0);
+            } else if (view instanceof ChannelImageOutputStream) {
+                ((ChannelImageOutputStream) view).output().seek(0);
             } else if (view instanceof Channel) {
                 /*
                  * Searches for a ChannelDataInput wrapping the channel, because it contains the original position
@@ -718,7 +721,6 @@ public class StorageConnector implements Serializable {
      *       <li>If the {@linkplain #getStorage() storage} object is an instance of the {@link Path},
      *           {@link File}, {@link URL}, {@link URI} or {@link CharSequence} types,
      *           returns the string representation of their path.</li>
-     *
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
@@ -727,7 +729,6 @@ public class StorageConnector implements Serializable {
      *       <li>If the {@linkplain #getStorage() storage} object is an instance of the {@link Path},
      *           {@link File}, {@link URL}, {@link URI} or {@link CharSequence} types and
      *           that type can be converted to the requested type, returned the conversion result.</li>
-     *
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
@@ -735,35 +736,29 @@ public class StorageConnector implements Serializable {
      *     <ul>
      *       <li>If the {@linkplain #getStorage() storage} object can be obtained as described in bullet 2 of the
      *           {@code DataInput} section below, then this method returns the associated byte buffer.</li>
-     *
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
      *   <li>{@link DataInput}:
      *     <ul>
      *       <li>If the {@linkplain #getStorage() storage} object is already an instance of {@code DataInput}
-     *           (including the {@link ImageInputStream} and {@link javax.imageio.stream.ImageOutputStream} types),
+     *           (including the {@link ImageInputStream} and {@link ImageOutputStream} types),
      *           then it is returned unchanged.</li>
-     *
      *       <li>Otherwise if the input is an instance of {@link ByteBuffer}, then an {@link ImageInputStream}
      *           backed by a read-only view of that buffer is created when first needed and returned.
      *           The properties (position, mark, limit) of the original buffer are unmodified.</li>
-     *
      *       <li>Otherwise if the input is an instance of {@link Path}, {@link File},
      *           {@link URI}, {@link URL}, {@link CharSequence}, {@link InputStream} or
-     *           {@link java.nio.channels.ReadableByteChannel}, then an {@link ImageInputStream} backed by a
+     *           {@link ReadableByteChannel}, then an {@link ImageInputStream} backed by a
      *           {@link ByteBuffer} is created when first needed and returned.</li>
-     *
      *       <li>Otherwise if {@link ImageIO#createImageInputStream(Object)} returns a non-null value,
      *           then this value is cached and returned.</li>
-     *
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
      *   <li>{@link ImageInputStream}:
      *     <ul>
      *       <li>If the above {@code DataInput} can be created and casted to {@code ImageInputStream}, returns it.</li>
-     *
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
@@ -771,10 +766,8 @@ public class StorageConnector implements Serializable {
      *     <ul>
      *       <li>If the {@linkplain #getStorage() storage} object is already an instance of {@link InputStream},
      *           then it is returned unchanged.</li>
-     *
      *       <li>Otherwise if the above {@code ImageInputStream} can be created,
      *           returns a wrapper around that stream.</li>
-     *
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
@@ -786,7 +779,31 @@ public class StorageConnector implements Serializable {
      *       <li>Otherwise if the above {@code InputStream} can be created, returns an {@link InputStreamReader}
      *           using the encoding specified by {@link OptionKey#ENCODING} if any, or using the system default
      *           encoding otherwise.</li>
-     *
+     *       <li>Otherwise this method returns {@code null}.</li>
+     *     </ul>
+     *   </li>
+     *   <li>{@link DataOutput}:
+     *     <ul>
+     *       <li>If the {@linkplain #getStorage() storage} object is already an instance of {@code DataOutput}
+     *           (including the {@link ImageOutputStream} type), then it is returned unchanged.</li>
+     *       <li>Otherwise if the output is an instance of {@link Path}, {@link File},
+     *           {@link URI}, {@link URL}, {@link CharSequence}, {@link OutputStream} or
+     *           {@link WritableByteChannel}, then an {@link ImageInputStream} backed by a
+     *           {@link ByteBuffer} is created when first needed and returned.</li>
+     *       <li>Otherwise if {@link ImageIO#createImageOutputStream(Object)} returns a non-null value,
+     *           then this value is cached and returned.</li>
+     *       <li>Otherwise this method returns {@code null}.</li>
+     *     </ul>
+     *   </li>
+     *   <li>{@link ImageOutputStream}:
+     *     <ul>
+     *       <li>If the above {@code DataOutput} can be created and casted to {@code ImageOutputStream}, returns it.</li>
+     *       <li>Otherwise this method returns {@code null}.</li>
+     *     </ul>
+     *   </li>
+     *   <li>{@link OutputStream}:
+     *     <ul>
+     *       <li>If the above {@code DataOutput} can be created and casted to {@code OutputStream}, returns it.</li>
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
@@ -794,10 +811,8 @@ public class StorageConnector implements Serializable {
      *     <ul>
      *       <li>If the {@linkplain #getStorage() storage} object is already an instance of {@link Connection},
      *           then it is returned unchanged.</li>
-     *
      *       <li>Otherwise if the storage is an instance of {@link DataSource}, then a connection is obtained
      *           when first needed and returned.</li>
-     *
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
@@ -805,7 +820,6 @@ public class StorageConnector implements Serializable {
      *     <ul>
      *       <li>If the storage given at construction time is already an instance of the requested type,
      *           returns it <i>as-is</i>.</li>
-     *
      *       <li>Otherwise this method throws {@link IllegalArgumentException}.</li>
      *     </ul>
      *   </li>
@@ -1109,6 +1123,11 @@ public class StorageConnector implements Serializable {
         } else if (wasProbingAbsentFile()) {
             return null;                            // Do not cache, for allowing file creation later.
         } else {
+            /*
+             * This block is executed for storages of unknown type, when `ChannelFactory` has no branch for that type.
+             * The Image I/O plugin mechanism allows users to create streams from arbitrary objets, so we delegate to
+             * it in last resort.
+             */
             reset();
             try {
                 asDataInput = ImageIO.createImageInputStream(storage);
@@ -1118,14 +1137,9 @@ public class StorageConnector implements Serializable {
             addView(DataInput.class, asDataInput, null, (byte) (CASCADE_ON_RESET | CASCADE_ON_CLOSE));
             /*
              * Note: Java Image I/O wrappers for Input/OutputStream do NOT close the underlying streams.
-             * This is a complication for us. We could mitigate the problem by subclassing the standard
-             * FileCacheImageInputStream and related classes, but we don't do that for now because this
-             * code should never be executed for InputStream storage. Instead, getChannelDataInput(true)
-             * should have created a ChannelImageInputStream or ChannelDataInput.
-             *
-             * In Apache SIS, ImageInputStream is used only by WorldFileStore. That store has its own
-             * mechanism for closing the stream used by ImageInputStream. It gives an extra safety in
-             * case the above paragraph does not hold.
+             * This is a complication for us. However in Apache SIS, `ImageInputStream` is used only
+             * by WorldFileStore. That store has its own mechanism for closing the underlying stream.
+             * So the problem described above would hopefully not occur in practice.
              */
         }
         return asDataInput;
@@ -1478,9 +1492,14 @@ public class StorageConnector implements Serializable {
         final DataOutput asDataOutput;
         if (out != null) {
             asDataOutput = out;
-            c = getView(ChannelDataOutput.class);   // Refresh because may have been added by createChannelDataInput().
+            c = getView(ChannelDataOutput.class);   // Refresh because may have been added by createChannelDataOutput().
             views.put(DataOutput.class, c);         // Share the same `Coupled` instance.
         } else {
+            /*
+             * This block is executed for storages of unknown type, when `ChannelFactory` has no branch for that type.
+             * The Image I/O plugin mechanism allows users to create streams from arbitrary objets, so we delegate to
+             * it in last resort.
+             */
             reset();
             try {
                 asDataOutput = ImageIO.createImageOutputStream(storage);
@@ -1490,18 +1509,42 @@ public class StorageConnector implements Serializable {
             addView(DataOutput.class, asDataOutput, null, (byte) (CASCADE_ON_RESET | CASCADE_ON_CLOSE));
             /*
              * Note: Java Image I/O wrappers for Input/OutputStream do NOT close the underlying streams.
-             * This is a complication for us. We could mitigate the problem by subclassing the standard
-             * FileCacheImageOutputStream and related classes, but we don't do that for now. A possible
-             * future evolution would be to complete ChannelImageOutputStream implementation instead.
-             *
-             * In Apache SIS, ImageOutputStream is used only by WorldFileStore. That store has its own
-             * mechanism for closing the stream used by ImageOutputStream. So the problem described in
-             * above paragraph would hopefully not occur in practice.
+             * This is a complication for us. However in Apache SIS, `ImageOutputStream` is used only
+             * by WorldFileStore. That store has its own mechanism for closing the underlying stream.
+             * So the problem described above would hopefully not occur in practice.
              */
         }
         return asDataOutput;
     }
 
+    /**
+     * Creates an {@link ImageOutputStream} from the {@link DataOutput} if possible. This method casts
+     * {@code DataOutput} if such cast is allowed, or upgrades {@link ChannelDataOutput} implementation.
+     *
+     * <p>This method is one of the {@link #OPENERS} methods and should be invoked at most once per
+     * {@code StorageConnector} instance.</p>
+     *
+     * @return input stream, or {@code null} if none or if {@linkplain #probing} result has been determined offline.
+     */
+    private ImageOutputStream createImageOutputStream() throws IOException, DataStoreException {
+        final ImageOutputStream asDataOutput;
+        DataOutput output = getStorageAs(DataOutput.class);
+        if (output instanceof ImageOutputStream) {
+            asDataOutput = (ImageOutputStream) output;
+            Coupled c = views.get(DataOutput.class);
+            views.put(ImageOutputStream.class, c);          // Share the same `Coupled` instance.
+        } else {
+            final ChannelDataOutput c = getStorageAs(ChannelDataOutput.class);
+            if (c != null && c.channel instanceof ReadableByteChannel) {
+                asDataOutput = new ChannelImageOutputStream(c);
+            } else {
+                asDataOutput = null;                        // Remember that there is no view.
+            }
+            addView(ImageOutputStream.class, asDataOutput, ChannelDataOutput.class, (byte) 0);
+        }
+        return asDataOutput;
+    }
+
     /**
      * Creates an output stream from {@link WritableByteChannel} if possible,
      * or from {@link ImageOutputStream} otherwise.


(sis) 03/03: Add compression support for GeoTIFF. Only "Deflate" is supported for now.

Posted by de...@apache.org.
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

commit 313bfa0ac1a207092290919a9b60600c60f8bbc6
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Mon Oct 30 22:44:25 2023 +0100

    Add compression support for GeoTIFF. Only "Deflate" is supported for now.
---
 .../main/module-info.java                          |   2 +-
 .../apache/sis/storage/geotiff/Compression.java    | 155 +++++++++++++++++++++
 .../apache/sis/storage/geotiff/GeoTiffStore.java   |  17 ++-
 .../sis/storage/geotiff/GeoTiffStoreProvider.java  |  12 +-
 .../org/apache/sis/storage/geotiff/Writer.java     |  18 ++-
 .../sis/storage/geotiff/base/Compression.java      |   2 +-
 .../storage/geotiff/writer/CompressionChannel.java |  84 +++++++++++
 .../sis/storage/geotiff/writer/TileMatrix.java     | 102 ++++++++++----
 .../org/apache/sis/storage/geotiff/writer/ZIP.java | 102 ++++++++++++++
 .../org/apache/sis/storage/geotiff/WriterTest.java |   1 +
 .../apache/sis/io/stream/ChannelDataOutput.java    |  10 ++
 .../org/apache/sis/util/resources/Vocabulary.java  |   5 +
 .../sis/util/resources/Vocabulary.properties       |   1 +
 .../sis/util/resources/Vocabulary_fr.properties    |   1 +
 14 files changed, 475 insertions(+), 37 deletions(-)

diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/module-info.java b/endorsed/src/org.apache.sis.storage.geotiff/main/module-info.java
index a2725a5357..105625c8c8 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/module-info.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/module-info.java
@@ -22,7 +22,7 @@
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Minh Chinh Vu (VNSC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   0.8
  */
 module org.apache.sis.storage.geotiff {
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Compression.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Compression.java
new file mode 100644
index 0000000000..51532f6ffa
--- /dev/null
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Compression.java
@@ -0,0 +1,155 @@
+/*
+ * 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.storage.geotiff;
+
+import java.io.Serializable;
+import java.util.OptionalInt;
+import java.util.zip.Deflater;
+import org.apache.sis.setup.OptionKey;
+import org.apache.sis.util.internal.Strings;
+import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.io.stream.InternalOptionKey;
+import org.apache.sis.util.ArgumentChecks;
+
+
+/**
+ * The compression method used for writing GeoTIFF files.
+ * This class specifies only the compressions supported by the Apache SIS writer.
+ * The Apache SIS reader supports more compression methods, but they are not listed in this class.
+ *
+ * <p>The compression to use can be specified as an option when opening the data store.
+ * For example for writing a TIFF file without compression, the following code can be used:</p>
+ *
+ * {@snippet lang="java" :
+ *     var file = Path.of("my_output_file.tiff");
+ *     var connector = new StorageConnector(file);
+ *     connector.setOption(Compression.OPTION_KEY, Compression.NONE);
+ *     try (GeoTiffStore ds = new GeoTiffStore(null, connector)) {
+ *         // Write data here.
+ *     }
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.5
+ * @since   1.5
+ */
+public final class Compression implements Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 3916905136793784898L;
+
+    /**
+     * No compression, but pack data into bytes as tightly as possible.
+     */
+    public static final Compression NONE = new Compression(org.apache.sis.storage.geotiff.base.Compression.NONE, 0);
+
+    /**
+     * Deflate compression, like ZIP format.
+     * This is the default compression method.
+     */
+    public static final Compression DEFLATE = new Compression(org.apache.sis.storage.geotiff.base.Compression.DEFLATE, Deflater.DEFAULT_COMPRESSION);
+
+    /**
+     * The key for declaring the compression at store creation time.
+     * See class Javadoc for usage example.
+     *
+     * @see StorageConnector#setOption(OptionKey, Object)
+     */
+    public static final OptionKey<Compression> OPTION_KEY = new InternalOptionKey<>("TIFF_COMPRESSION", Compression.class);
+
+    /**
+     * The compression method.
+     */
+    final org.apache.sis.storage.geotiff.base.Compression method;
+
+    /**
+     * The compression level, or -1 for default.
+     */
+    final int level;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param  method  the compression method.
+     */
+    private Compression(final org.apache.sis.storage.geotiff.base.Compression method, final int level) {
+        this.method = method;
+        this.level  = level;
+    }
+
+    /**
+     * Returns an instance with the specified compression level.
+     * Value 0 means no compression. A value of -1 resets the default compression.
+     *
+     * @param  value  the new compression level (0-9).
+     * @return a compression of the specified level.
+     */
+    public Compression withLevel(final int value) {
+        if (value == level) return this;
+        ArgumentChecks.ensureBetween("level", Deflater.DEFAULT_COMPRESSION, Deflater.BEST_COMPRESSION, value);
+        return new Compression(method, value);
+    }
+
+    /**
+     * Returns the current compression level.
+     *
+     * @return the current compression level, or an empty value for the default level.
+     */
+    public OptionalInt level() {
+        return (level >= 0) ? OptionalInt.of(level) : OptionalInt.empty();
+    }
+
+    /*
+     * TODO: add `withPredictor(Predictor)` method.
+     */
+
+    /**
+     * Compares this compression with the given object for equality.
+     *
+     * @param  other  the object to compare with this compression.
+     * @return whether the two objects are equal.
+     */
+    @Override
+    public boolean equals(final Object other) {
+        if (other instanceof Compression) {
+            final var c = (Compression) other;
+            return method.equals(c.method) && level == c.level;
+        }
+        return false;
+    }
+
+    /**
+     * Returns a hash code value for this compression.
+     *
+     * @return a hash code value.
+     */
+    @Override
+    public int hashCode() {
+        return method.hashCode() + level;
+    }
+
+    /**
+     * Returns a string representation of this compression.
+     *
+     * @return a string representation for debugging purposes.
+     */
+    @Override
+    public String toString() {
+        return Strings.toString(Compression.class, "method", method,
+                "level", (level != 0) ? Integer.valueOf(level) : null);
+    }
+}
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
index 005a7855cd..5385f1406f 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
@@ -103,6 +103,11 @@ public class GeoTiffStore extends DataStore implements Aggregate {
      */
     private volatile Writer writer;
 
+    /**
+     * The compression to apply when writing tiles, or {@code null} if unspecified.
+     */
+    final Compression compression;
+
     /**
      * The locale to use for formatting metadata. This is not necessarily the same as {@link #getLocale()},
      * which is about formatting error messages. A null value means "unlocalized", which is usually English.
@@ -240,10 +245,11 @@ public class GeoTiffStore extends DataStore implements Aggregate {
         final Charset encoding = connector.getOption(OptionKey.ENCODING);
         this.encoding = (encoding != null) ? encoding : StandardCharsets.US_ASCII;
 
-        dataLocale = connector.getOption(OptionKey.LOCALE);
-        timezone   = connector.getOption(OptionKey.TIMEZONE);
-        location   = connector.getStorageAs(URI.class);
-        path       = connector.getStorageAs(Path.class);
+        compression = connector.getOption(Compression.OPTION_KEY);
+        dataLocale  = connector.getOption(OptionKey.LOCALE);
+        timezone    = connector.getOption(OptionKey.TIMEZONE);
+        location    = connector.getStorageAs(URI.class);
+        path        = connector.getStorageAs(Path.class);
         try {
             if (URIDataStore.Provider.isWritable(connector, true)) {
                 ChannelDataOutput output = connector.commit(ChannelDataOutput.class, Constants.GEOTIFF);
@@ -323,6 +329,9 @@ public class GeoTiffStore extends DataStore implements Aggregate {
                 if (!options.isEmpty()) {
                     param.parameter(GeoTiffStoreProvider.OPTIONS).setValue(options.toArray(GeoTiffOption[]::new));
                 }
+                if (compression != null) {
+                    param.parameter(GeoTiffStoreProvider.COMPRESSION).setValue(compression);
+                }
             }
         }
         return Optional.ofNullable(param);
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStoreProvider.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStoreProvider.java
index b7a5c5d723..409faf5e23 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStoreProvider.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStoreProvider.java
@@ -78,14 +78,20 @@ public class GeoTiffStoreProvider extends DataStoreProvider {
      */
     static final String OPTIONS = "options";
 
+    /**
+     * Name of the parameter for specifying the compression.
+     */
+    static final String COMPRESSION = "compression";
+
     /**
      * The parameter descriptor to be returned by {@link #getOpenParameters()}.
      */
     private static final ParameterDescriptorGroup OPEN_DESCRIPTOR;
     static {
-        final var builder = new ParameterBuilder();
-        final var options = builder.addName(OPTIONS).setDescription(Vocabulary.formatInternational(Vocabulary.Keys.Options)).create(GeoTiffOption[].class, null);
-        OPEN_DESCRIPTOR = builder.addName(Constants.GEOTIFF).createGroup(URIDataStore.Provider.LOCATION_PARAM, options);
+        final var builder     = new ParameterBuilder();
+        final var options     = builder.addName(OPTIONS).setDescription(Vocabulary.formatInternational(Vocabulary.Keys.Options)).create(GeoTiffOption[].class, null);
+        final var compression = builder.addName(COMPRESSION).setDescription(Vocabulary.formatInternational(Vocabulary.Keys.Compression)).create(Compression.class, null);
+        OPEN_DESCRIPTOR = builder.addName(Constants.GEOTIFF).createGroup(URIDataStore.Provider.LOCATION_PARAM, options, compression);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java
index f9644ed632..f760802872 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java
@@ -27,6 +27,7 @@ import java.util.List;
 import java.util.Deque;
 import java.util.Queue;
 import java.util.Set;
+import java.util.zip.Deflater;
 import java.awt.image.RenderedImage;
 import java.awt.image.SampleModel;
 import java.awt.image.BandedSampleModel;
@@ -48,6 +49,7 @@ import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.internal.Numerics;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.math.Fraction;
+import org.apache.sis.storage.geotiff.base.Compression;
 import org.apache.sis.storage.geotiff.writer.TagValue;
 import org.apache.sis.storage.geotiff.writer.TileMatrix;
 import org.apache.sis.storage.geotiff.writer.GeoEncoder;
@@ -365,6 +367,18 @@ final class Writer extends IOBase implements Flushable {
          */
         final Fraction xres = new Fraction(1, 1);       // TODO
         final Fraction yres = xres;
+        /*
+         * Compression.
+         */
+        final Compression compression;
+        final int compressionLevel;
+        if (store.compression != null) {
+            compressionLevel = store.compression.level;
+            compression = (compressionLevel != 0) ? store.compression.method : Compression.NONE;
+        } else {
+            compression = Compression.DEFLATE;              // Default value documented in `Compression` Javadoc.
+            compressionLevel = Deflater.DEFAULT_COMPRESSION;
+        }
         /*
          * If the image has any unsupported feature, the exception should have been thrown before this point.
          * Now start writing the entries. The entries in an IFD must be sorted in ascending order by tag code.
@@ -380,7 +394,7 @@ final class Writer extends IOBase implements Flushable {
         writeTag((short) TAG_IMAGE_WIDTH,                (short) TIFFTag.TIFF_LONG,  image.visibleBands.getWidth());
         writeTag((short) TAG_IMAGE_LENGTH,               (short) TIFFTag.TIFF_LONG,  image.visibleBands.getHeight());
         writeTag((short) TAG_BITS_PER_SAMPLE,            (short) TIFFTag.TIFF_SHORT, bitsPerSample);
-        writeTag((short) TAG_COMPRESSION,                (short) TIFFTag.TIFF_SHORT, COMPRESSION_NONE);
+        writeTag((short) TAG_COMPRESSION,                (short) TIFFTag.TIFF_SHORT, compression.code);
         writeTag((short) TAG_PHOTOMETRIC_INTERPRETATION, (short) TIFFTag.TIFF_SHORT, colorInterpretation);
         writeTag((short) TAG_DOCUMENT_NAME,              /* TIFF_ASCII */            mf.series);
         writeTag((short) TAG_IMAGE_DESCRIPTION,          /* TIFF_ASCII */            mf.title);
@@ -399,7 +413,7 @@ final class Writer extends IOBase implements Flushable {
         if (colorInterpretation == PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR) {
             writeColorPalette((IndexColorModel) image.visibleBands.getColorModel(), 1L << bitsPerSample[0]);
         }
-        final var tiling = new TileMatrix(image.visibleBands, numPlanes, bitsPerSample, offsetIFD);
+        final var tiling = new TileMatrix(image.visibleBands, numPlanes, bitsPerSample, offsetIFD, compression, compressionLevel);
         writeTag((short) TAG_TILE_WIDTH,  (short) TIFFTag.TIFF_LONG, tiling.tileWidth);
         writeTag((short) TAG_TILE_LENGTH, (short) TIFFTag.TIFF_LONG, tiling.tileHeight);
         tiling.offsetsTag = writeTag((short) TAG_TILE_OFFSETS, tiling.offsets);
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/base/Compression.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/base/Compression.java
index 17f0b0ab49..629b895585 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/base/Compression.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/base/Compression.java
@@ -121,7 +121,7 @@ public enum Compression {
     /**
      * The TIFF code for this compression.
      */
-    final int code;
+    public final int code;
 
     /**
      * Creates a new compression enumeration.
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/CompressionChannel.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/CompressionChannel.java
new file mode 100644
index 0000000000..46bcb56b4a
--- /dev/null
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/CompressionChannel.java
@@ -0,0 +1,84 @@
+/*
+ * 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.storage.geotiff.writer;
+
+import java.io.IOException;
+import java.nio.channels.WritableByteChannel;
+import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.io.stream.ChannelDataOutput;
+
+
+/**
+ * Deflater using a temporary buffer where to compress data before writing to the channel.
+ * This class does not need to care about subsampling.
+ *
+ * <p>The {@link #close()} method shall be invoked when this channel is no longer used.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+abstract class CompressionChannel implements WritableByteChannel {
+    /**
+     * Desired size of the temporary buffer where to compress data.
+     */
+    static final int BUFFER_SIZE = StorageConnector.DEFAULT_BUFFER_SIZE / 2;
+
+    /**
+     * The destination where to write compressed data.
+     */
+    protected final ChannelDataOutput output;
+
+    /**
+     * Creates a new channel which will compress data to the given output.
+     *
+     * @param  output  the destination of compressed data.
+     */
+    protected CompressionChannel(final ChannelDataOutput output) {
+        this.output = output;
+    }
+
+    /**
+     * Tells whether this channel is still open.
+     */
+    @Override
+    public final boolean isOpen() {
+        return output.channel.isOpen();
+    }
+
+    /**
+     * Writes any pending data and reset the deflater for the next tile to compress.
+     *
+     * @param  owner  the data output which is writing in this channel.
+     * @throws IOException if an error occurred while writing to the underlying output channel.
+     */
+    public void finish(final ChannelDataOutput owner) throws IOException {
+        assert owner.channel == this;
+        owner.flush();
+        owner.clear();
+    }
+
+    /**
+     * Releases resources used by this channel, but <strong>without</strong> closing the {@linkplain #output} channel.
+     * The {@linkplain #output} channel is not closed by this operation because it will typically be needed again for
+     * compressing other tiles.
+     *
+     * @throws IOException if an error occurred while flushing last data to the channel.
+     */
+    @Override
+    public void close() throws IOException {
+        // Do NOT close `output`.
+    }
+}
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/TileMatrix.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/TileMatrix.java
index ed080fb0ef..f3cda439a8 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/TileMatrix.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/TileMatrix.java
@@ -18,6 +18,7 @@ package org.apache.sis.storage.geotiff.writer;
 
 import java.util.Arrays;
 import java.io.IOException;
+import java.nio.ByteBuffer;
 import java.awt.Rectangle;
 import java.awt.image.Raster;
 import java.awt.image.RenderedImage;
@@ -36,6 +37,9 @@ import org.apache.sis.image.DataType;
 import org.apache.sis.util.internal.Numerics;
 import org.apache.sis.io.stream.ChannelDataOutput;
 import org.apache.sis.io.stream.HyperRectangleWriter;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.geotiff.base.Compression;
+import org.apache.sis.storage.geotiff.base.Resources;
 
 
 /**
@@ -95,22 +99,35 @@ public final class TileMatrix {
      */
     public TagValue offsetsTag, lengthsTag;
 
+    /**
+     * The compression to use for writing tiles.
+     */
+    private final Compression compression;
+
+    /**
+     * The compression level, or -1 for default.
+     */
+    private final int compressionLevel;
+
     /**
      * Creates a new set of information about tiles to write.
      *
-     * @param image          the image to write.
-     * @param numBands       the number of bands.
-     * @param bitsPerSample  number of bits per sample to write.
-     * @param isPlanar       whether the planar configuration is to store bands in separated planes.
-     * @param offsetIFD      offset in {@link ChannelDataOutput} where the IFD starts.
+     * @param image             the image to write.
+     * @param numPlanes         the number of banks (plane in TIFF terminology).
+     * @param bitsPerSample     number of bits per sample to write.
+     * @param offsetIFD         offset in {@link ChannelDataOutput} where the IFD starts.
+     * @param compression       the compression method to apply.
+     * @param compressionLevel  compression level (0-9), or -1 for the default.
      */
     public TileMatrix(final RenderedImage image, final int numPlanes, final int[] bitsPerSample,
-                      final long offsetIFD)
+                      final long offsetIFD, final Compression compression, final int compressionLevel)
     {
         final int pixelSize, numArrays;
-        this.offsetIFD = offsetIFD;
-        this.numPlanes = numPlanes;
-        this.image     = image;
+        this.offsetIFD        = offsetIFD;
+        this.numPlanes        = numPlanes;
+        this.image            = image;
+        this.compression      = compression;
+        this.compressionLevel = compressionLevel;
         type       = DataType.forBands(image);
         tileWidth  = image.getTileWidth();
         tileHeight = image.getTileHeight();
@@ -141,28 +158,57 @@ public final class TileMatrix {
         }
     }
 
+    /**
+     * Creates the data output stream to use for writing compressed data.
+     *
+     * @param  output  where to write compressed bytes.
+     * @throws DataStoreException if the compression method is unsupported.
+     * @throws IOException if an error occurred while creating the data channel.
+     * @return the data output for compressing data, or {@code output} if uncompressed.
+     */
+    private ChannelDataOutput createCompressionChannel(final ChannelDataOutput output)
+            throws DataStoreException, IOException
+    {
+        final CompressionChannel channel;
+        boolean isDirect = false;           // `true` if using a native library which accepts NIO buffers.
+        switch (compression) {
+            case NONE:    return output;
+            case DEFLATE: channel = new ZIP(output, compressionLevel); isDirect = true; break;
+            default: throw new DataStoreException(Resources.forLocale(null)
+                    .getString(Resources.Keys.UnsupportedCompressionMethod_1, compression));
+        }
+        final int capacity = CompressionChannel.BUFFER_SIZE;
+        ByteBuffer buffer = isDirect ? ByteBuffer.allocateDirect(capacity) : ByteBuffer.allocate(capacity);
+        return new ChannelDataOutput(output.filename, channel, buffer.order(output.buffer.order()));
+    }
+
     /**
      * Writes all tiles of the image.
      * Caller shall invoke {@link #writeOffsetsAndLengths(ChannelDataOutput)} after this method.
      * This invocation is not done by this method for allowing the caller to control when to write data.
      *
      * @param  output  where to write the tiles data.
+     * @throws DataStoreException if the compression method is unsupported.
      * @throws IOException if an error occurred while writing to the given output.
      */
-    public void writeRasters(final ChannelDataOutput output) throws IOException {
+    public void writeRasters(final ChannelDataOutput output) throws DataStoreException, IOException {
+        final ChannelDataOutput compress = createCompressionChannel(output);
+        final CompressionChannel cc = (compress != output) ? (CompressionChannel) compress.channel : null;
+
         SampleModel sm = null;
         int[] bankIndices = null;
         HyperRectangleWriter rect = null;
         final int minTileX = image.getMinTileX();
         final int minTileY = image.getMinTileY();
         int planeIndex = 0;
-        for (int i=0; i < offsets.length; i++) {
+        while (planeIndex < offsets.length) {
             /*
              * In current implementation, we iterate from left to right then top to bottom.
              * But a future version could use Hilbert iterator (for example).
              */
-            int tileX = i % numXTiles;
-            int tileY = i / numXTiles;
+            final int tileIndex = planeIndex / numPlanes;
+            int tileX = tileIndex % numXTiles;
+            int tileY = tileIndex / numXTiles;
             tileX += minTileX;
             tileY += minTileY;
             final Raster tile = image.getTile(tileX, tileY);
@@ -186,25 +232,29 @@ public final class TileMatrix {
             if (rect == null) {
                 throw new UnsupportedOperationException();      // TODO: reformat using a recycled Raster.
             }
-            final long position = output.getStreamPosition();
             final DataBuffer buffer = tile.getDataBuffer();
             final int[] bufferOffsets = buffer.getOffsets();
-            for (int j=0; j < numPlanes; j++) {
-                final int b = bankIndices[j];
-                final int offset = bufferOffsets[b];
+            for (int j=0; j<numPlanes; j++) {
+                final int  b        = bankIndices[j];
+                final int  offset   = bufferOffsets[b];
+                final long position = output.getStreamPosition();
                 switch (type) {
-                    case BYTE:   rect.write(output, ((DataBufferByte)   buffer).getData(b), offset); break;
-                    case USHORT: rect.write(output, ((DataBufferUShort) buffer).getData(b), offset); break;
-                    case SHORT:  rect.write(output, ((DataBufferShort)  buffer).getData(b), offset); break;
-                    case INT:    rect.write(output, ((DataBufferInt)    buffer).getData(b), offset); break;
-                    case FLOAT:  rect.write(output, ((DataBufferFloat)  buffer).getData(b), offset); break;
-                    case DOUBLE: rect.write(output, ((DataBufferDouble) buffer).getData(b), offset); break;
+                    case BYTE:   rect.write(compress, ((DataBufferByte)   buffer).getData(b), offset); break;
+                    case USHORT: rect.write(compress, ((DataBufferUShort) buffer).getData(b), offset); break;
+                    case SHORT:  rect.write(compress, ((DataBufferShort)  buffer).getData(b), offset); break;
+                    case INT:    rect.write(compress, ((DataBufferInt)    buffer).getData(b), offset); break;
+                    case FLOAT:  rect.write(compress, ((DataBufferFloat)  buffer).getData(b), offset); break;
+                    case DOUBLE: rect.write(compress, ((DataBufferDouble) buffer).getData(b), offset); break;
+                }
+                if (cc != null) {
+                    cc.finish(compress);
                 }
+                offsets[planeIndex] = position;
+                lengths[planeIndex] = Math.toIntExact(Math.subtractExact(output.getStreamPosition(), position));
+                planeIndex++;
             }
-            offsets[planeIndex] = position;
-            lengths[planeIndex] = Math.toIntExact(Math.subtractExact(output.getStreamPosition(), position));
-            planeIndex++;
         }
+        if (cc != null) cc.close();
         if (planeIndex != offsets.length) {
             throw new AssertionError();                 // Should never happen.
         }
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/ZIP.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/ZIP.java
new file mode 100644
index 0000000000..abb85234fd
--- /dev/null
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/ZIP.java
@@ -0,0 +1,102 @@
+/*
+ * 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.storage.geotiff.writer;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.zip.Deflater;
+import org.apache.sis.util.ArraysExt;
+import org.apache.sis.io.stream.ChannelDataOutput;
+
+
+/**
+ * Deflater for values encoded with the "Deflate" compression.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+final class ZIP extends CompressionChannel {
+    /**
+     * Access to the ZLIB compression library.
+     * Must be released by call to {@link Deflater#end()} after decompression is completed.
+     */
+    private final Deflater deflater;
+
+    /**
+     * Creates a new channel which will compress data to the given output.
+     *
+     * @param  output  the destination of compressed data.
+     * @param  level   the compression level.
+     */
+    public ZIP(final ChannelDataOutput output, final int level) {
+        super(output);
+        deflater = new Deflater(level);
+        deflater.setStrategy(Deflater.FILTERED);
+    }
+
+    /**
+     * Compresses some bytes from the given buffer to the {@linkplain #output output}.
+     *
+     * @param  source  the buffer from which bytes are to be transferred.
+     * @return the number of uncompressed bytes written.
+     * @throws IOException if an error occurred while writing to the underlying output channel.
+     */
+    @Override
+    public int write(final ByteBuffer source) throws IOException {
+        final ByteBuffer target = output.buffer;
+        final int start = source.position();
+        deflater.setInput(source);
+        int remaining;
+        while ((remaining = source.remaining()) > 0) {
+            assert !deflater.needsInput();
+            output.ensureBufferAccepts(Math.min(remaining, target.capacity()));
+            target.limit(target.capacity());        // Allow the use of all available space.
+            deflater.deflate(target);
+            target.limit(target.position());        // Bytes after the position are not valid.
+        }
+        return source.position() - start;   // Number from caller's perspective (it doesn't know about compression).
+    }
+
+    /**
+     * Writes any pending data and reset the deflater for the next tile to compress.
+     *
+     * @param  owner  the data output which is writing in this channel.
+     * @throws IOException if an error occurred while writing to the underlying output channel.
+     */
+    @Override
+    public void finish(final ChannelDataOutput owner) throws IOException {
+        deflater.finish();
+        super.finish(owner);
+        final ByteBuffer target = output.buffer;
+        while (!deflater.finished()) {
+            output.ensureBufferAccepts(Math.min(target.capacity(), BUFFER_SIZE));
+            target.limit(target.capacity());            // Allow the use of all available space.
+            deflater.setInput(ArraysExt.EMPTY_BYTE);
+            deflater.finish();
+            deflater.deflate(target);
+            target.limit(target.position());            // Bytes after the position are not valid.
+        }
+        deflater.reset();
+    }
+
+    /**
+     * Releases resources used by the deflater.
+     */
+    @Override
+    public void close() {
+        deflater.end();
+    }
+}
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/WriterTest.java b/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/WriterTest.java
index 6995521cc0..e0d6c2ec74 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/WriterTest.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/WriterTest.java
@@ -146,6 +146,7 @@ public final class WriterTest extends TestCase {
         var d = new ChannelDataOutput("TIFF", output, ByteBuffer.allocate(random.nextInt(128) + 20).order(order));
         var c = new StorageConnector(d);
         c.setOption(GeoTiffOption.OPTION_KEY, options);
+        c.setOption(Compression.OPTION_KEY, Compression.NONE);
         store = new GeoTiffStore(null, c);
         data  = output.toBuffer().order(order);
     }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelDataOutput.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelDataOutput.java
index 08300cdf1b..c88a74e8f0 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelDataOutput.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelDataOutput.java
@@ -916,4 +916,14 @@ public class ChannelDataOutput extends ChannelData implements DataOutput, Flusha
          * (see ChannelDataInput.yield(…) for code example). For now it is not needed.
          */
     }
+
+    /**
+     * Clears the buffer and set the position to 0.
+     * This method does not read or write any byte.
+     */
+    public final void clear() {
+        buffer.clear().limit(0);
+        bufferOffset = 0;
+        bitPosition  = 0;
+    }
 }
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
index 1ec1a11bb2..c56c3ff649 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
@@ -234,6 +234,11 @@ public class Vocabulary extends IndexedResourceBundle {
          */
         public static final short Commands = 31;
 
+        /**
+         * Compression
+         */
+        public static final short Compression = 273;
+
         /**
          * Configuration
          */
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
index 471578feb2..3c760931aa 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
@@ -50,6 +50,7 @@ Color                   = Color
 Colors                  = Colors
 ColorIndex              = Color index
 Commands                = Commands
+Compression             = Compression
 Controls                = Controls
 Configuration           = Configuration
 Constants               = Constants
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
index a8ec92bc4b..0061fa151a 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
@@ -57,6 +57,7 @@ Color                   = Couleur
 Colors                  = Couleurs
 ColorIndex              = Indice de couleur
 Commands                = Commandes
+Compression             = Compression
 Controls                = Contr\u00f4les
 Configuration           = Configuration
 Constants               = Constantes


(sis) 01/03: Replace `api` dependency to Glassfish by a pair of `compileOnly` and `runtimeOnly`. The intend is to avoid that this dependency appears in Maven pom files.

Posted by de...@apache.org.
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

commit f7dabc89f15b747bbd9a7c840b8281a015f83752
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Mon Oct 30 10:19:57 2023 +0100

    Replace `api` dependency to Glassfish by a pair of `compileOnly` and `runtimeOnly`.
    The intend is to avoid that this dependency appears in Maven pom files.
---
 endorsed/build.gradle.kts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/endorsed/build.gradle.kts b/endorsed/build.gradle.kts
index 380a387a90..828fc2b9d5 100644
--- a/endorsed/build.gradle.kts
+++ b/endorsed/build.gradle.kts
@@ -58,7 +58,8 @@ dependencies {
     api(libs.jaxb.api)
 
     // Optional dependencies
-    api(libs.jaxb.impl)                 // Actually runtime only, but declared as API for avoiding compiler warnings.
+    runtimeOnly(libs.jaxb.impl)
+    compileOnly(libs.jaxb.impl)                 // For avoiding compiler warnings. Not read by SIS modules.
     compileOnly(libs.jts.core)
     compileOnly(libs.esri.geometry)
     compileOnly(libs.libreoffice)