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/04/18 14:42:29 UTC

[sis-site] branch main updated (ec90f7ec -> 1a25795a)

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

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


    from ec90f7ec Reorganize the "how to" examples with better separation of raster use cases.
     new eb53c546 Clarify in which conditions the `GridCoverage` can be used outside the `try` block.
     new f3e04ec8 Add more "how to" examples for raster data. Add clarification about pixel coordinates and which exceptions are thrown.
     new 1a25795a Add two more examples in the referencing section.

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:
 content/howto/_index.md                            |   4 +
 content/howto/datalake_to_datacube.md              | 129 +++++++++++++++++++++
 content/howto/geodetic_paths.md                    |  68 +++++++++++
 content/howto/parallel_computation.md              |  60 ++++++++++
 content/howto/parse_and_format_mgrs_codes.md       |  75 ++++++++++++
 .../howto/raster_values_at_pixel_coordinates.md    |   7 +-
 content/howto/rasters_bigger_than_memory.md        |   8 ++
 content/howto/read_geotiff.md                      |  19 ++-
 content/howto/read_netcdf.md                       |  24 +++-
 content/howto/resample_raster.md                   |   2 -
 10 files changed, 390 insertions(+), 6 deletions(-)
 create mode 100644 content/howto/datalake_to_datacube.md
 create mode 100644 content/howto/geodetic_paths.md
 create mode 100644 content/howto/parallel_computation.md
 create mode 100644 content/howto/parse_and_format_mgrs_codes.md


[sis-site] 03/03: Add two more examples in the referencing section.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/sis-site.git

commit 1a25795a572d662138160e11f197702f63d3df98
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Apr 18 16:41:51 2023 +0200

    Add two more examples in the referencing section.
---
 content/howto/_index.md                      |  2 +
 content/howto/geodetic_paths.md              | 68 +++++++++++++++++++++++++
 content/howto/parse_and_format_mgrs_codes.md | 75 ++++++++++++++++++++++++++++
 3 files changed, 145 insertions(+)

diff --git a/content/howto/_index.md b/content/howto/_index.md
index 35b52100..8d717856 100644
--- a/content/howto/_index.md
+++ b/content/howto/_index.md
@@ -28,8 +28,10 @@ The examples are grouped in the following sections:
 * [Get the EPSG code or URN of an existing reference system](howto/lookup_crs_urn.html)
 * [Transform points between two reference systems](howto/transform_coordinates.html)
 * [Transform envelopes between two reference systems](howto/transform_envelopes.html)
+* [Parse and format MGRS codes](howto/parse_and_format_mgrs_codes.html)
 * [Union or intersection of envelopes in different reference systems](howto/envelopes_in_different_crs.html)
 * [Determine if two reference systems are functionally equal](howto/crs_equality.html)
+* [Compute geodetic distances and paths](howto/geodetic_paths.html)
 
 
 # Metadata    {#metadata}
diff --git a/content/howto/geodetic_paths.md b/content/howto/geodetic_paths.md
new file mode 100644
index 00000000..353b9063
--- /dev/null
+++ b/content/howto/geodetic_paths.md
@@ -0,0 +1,68 @@
+---
+title: Compute geodetic distances and paths
+---
+
+The following example computes the geodetic distance between given positions.
+The geodetic distance is the shortest distance on Earth ellipsoid.
+Apache SIS can also compute the path as a Béziers curve,
+with the property that the azimuths at the two curve extremities are preserved.
+
+
+# Direct dependencies
+
+Maven coordinates                        | Module info                  | Remarks
+---------------------------------------- | ---------------------------- | -------
+`org.apache.sis.storage:sis-referencing` | `org.apache.sis.referencing` |
+
+
+# Code example
+
+Note that all geographic coordinates below express latitude *before* longitude.
+
+{{< highlight java >}}
+import java.awt.Shape;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.GeodeticCalculator;
+
+public class GeodeticPaths {
+    /**
+     * Demo entry point.
+     *
+     * @param  args  ignored.
+     */
+    public static void main(String[] args) {
+        var calculator = GeodeticCalculator.create(CommonCRS.WGS84.geographic());
+        calculator.setStartGeographicPoint(40, 5);
+        calculator.setEndGeographicPoint(42, 3);
+        System.out.printf("Result of geodetic calculation: %s%n", calculator);
+
+        double d;
+        d  = calculator.getRhumblineLength();
+        d -= calculator.getGeodesicDistance();
+        System.out.printf("The rhumbline is %1.2f %s longer%n", d, calculator.getDistanceUnit());
+
+        Shape path = calculator.createGeodesicPath2D(100);
+        System.out.printf("Java2D shape class for approximating this path: %s%n", path.getClass());
+    }
+}
+{{< / highlight >}}
+
+
+# Output
+
+The output depends on the locale.
+Below is an example:
+
+```
+Result of geodetic calculation:
+Coordinate reference system: WGS 84
+┌─────────────┬─────────────────┬────────────────┬────────────┐
+│             │    Latitude     │   Longitude    │  Azimuth   │
+│ Start point │ 40°00′00.0000″N │ 5°00′00.0000″E │ -36°29′45″ │
+│ End point   │ 42°00′00.0000″N │ 3°00′00.0000″E │ -37°48′29″ │
+└─────────────┴─────────────────┴────────────────┴────────────┘
+Geodesic distance: 278,632.68 m
+
+The rhumbline is 6.09 m longer
+Java2D shape class for approximating this path: class java.awt.geom.QuadCurve2D$Double
+```
diff --git a/content/howto/parse_and_format_mgrs_codes.md b/content/howto/parse_and_format_mgrs_codes.md
new file mode 100644
index 00000000..60dece71
--- /dev/null
+++ b/content/howto/parse_and_format_mgrs_codes.md
@@ -0,0 +1,75 @@
+---
+title: Parse and format MGRS codes
+---
+
+The following example converts geographic coordinates to
+Military Grid Reference System (MGRS) codes and conversely.
+MGRS codes can be seen as a kind of GeoHash but with better properties.
+Apache SIS supports also GeoHash if desired, in a way similar to this example.
+
+
+# Direct dependencies
+
+Maven coordinates                        | Module info                  | Remarks
+---------------------------------------- | ---------------------------- | -------
+`org.apache.sis.storage:sis-referencing` | `org.apache.sis.referencing` |
+
+
+# Code example
+
+Note that all geographic coordinates below express latitude *before* longitude.
+
+{{< highlight java >}}
+import org.apache.sis.geometry.DirectPosition2D;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.gazetteer.MilitaryGridReferenceSystem;
+import org.opengis.referencing.gazetteer.Location;
+import org.opengis.referencing.operation.TransformException;
+
+public class MGRS {
+    /**
+     * Demo entry point.
+     *
+     * @param  args  ignored.
+     * @throws TransformException if an error occurred when encoding or decoding a position.
+     */
+    public static void main(String[] args) throws  TransformException {
+        var rs    = new MilitaryGridReferenceSystem();
+        var point = new DirectPosition2D(CommonCRS.WGS84.geographic(), 40, 5);
+        var coder = rs.createCoder();
+        var code  = coder.encode(point);
+        System.out.printf("MGRS code of %s is %s%n", point, code);
+
+        coder.setPrecision(1000);           // Limit to a precision of 1 km.
+        code = coder.encode(point);
+        System.out.printf("Same code reduced to 1 km precision: %s%n", code);
+
+        Location reverse = coder.decode(code);
+        System.out.printf("Back to geographic coordinates: %s%n", reverse);
+    }
+}
+{{< / highlight >}}
+
+
+# Output
+
+The output depends on the locale.
+Below is an example:
+
+```
+MGRS code of POINT(40 5) is 31TFE7072529672
+Same code reduced to 1 km precision: 31TFE7029
+Back to geographic coordinates:
+┌─────────────────────────────────────────────────────────────────┐
+│ Location type:               Grid coordinate                    │
+│ Geographic identifier:       31TFE7029                          │
+│ West bound:                    670,000 m    —     4°59′28″E     │
+│ Representative value:          670,500 m    —     4°59′51″E     │
+│ East bound:                    671,000 m    —     5°00′12″E     │
+│ South bound:                 4,429,656 m    —    40°00′00″N     │
+│ Representative value:        4,429,828 m    —    40°00′05″N     │
+│ North bound:                 4,430,000 m    —    40°00′12″N     │
+│ Coordinate reference system: WGS 84 / UTM zone 31N              │
+│ Administrator:               North Atlantic Treaty Organization │
+└─────────────────────────────────────────────────────────────────┘
+```


[sis-site] 02/03: Add more "how to" examples for raster data. Add clarification about pixel coordinates and which exceptions are thrown.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/sis-site.git

commit f3e04ec8101161a732a9d999508bed45d7bed297
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Apr 18 15:30:06 2023 +0200

    Add more "how to" examples for raster data.
    Add clarification about pixel coordinates and which exceptions are thrown.
---
 content/howto/_index.md                            |   2 +
 content/howto/datalake_to_datacube.md              | 129 +++++++++++++++++++++
 content/howto/parallel_computation.md              |  60 ++++++++++
 .../howto/raster_values_at_pixel_coordinates.md    |   7 +-
 content/howto/read_netcdf.md                       |   2 +-
 content/howto/resample_raster.md                   |   2 -
 6 files changed, 198 insertions(+), 4 deletions(-)

diff --git a/content/howto/_index.md b/content/howto/_index.md
index 6292d3f8..35b52100 100644
--- a/content/howto/_index.md
+++ b/content/howto/_index.md
@@ -16,6 +16,8 @@ The examples are grouped in the following sections:
 * [Get raster values at geographic coordinates](howto/raster_values_at_geographic_coordinates.html)
 * [Handle rasters bigger than memory](howto/rasters_bigger_than_memory.html)
 * [Resample a raster](howto/resample_raster.html)
+* [Parallel computation](howto/parallel_computation.html)
+* [From data lake to data cube](howto/datalake_to_datacube.html)
 * [Write a raster to a file](howto/write_raster.html)
 
 
diff --git a/content/howto/datalake_to_datacube.md b/content/howto/datalake_to_datacube.md
new file mode 100644
index 00000000..7a0b489a
--- /dev/null
+++ b/content/howto/datalake_to_datacube.md
@@ -0,0 +1,129 @@
+---
+title: From data lake to data cube
+---
+
+This example opens a few files where each file represent a slice in a data cube.
+Then the slices are aggregated together in a single multi-dimensional data cube.
+For example each file may be a raster representing Sea Surface Temperature (SST) at a specific day,
+and those files can be a aggregated in a single three-dimensional raster with a temporal dimension.
+
+A current limitation is that each slice must have the same number of dimensions than the data cube.
+For the example of SST raster for a specific day, the raster CRS must still have a temporal axis
+even if the grid contains only one cell in the temporal dimension.
+A future Apache SIS version will provide methods for adding dimensions.
+
+This example assumes that all slice have the same size, resolution and coordinate reference system.
+If this is not the case, the aggregation will still work but instead of producing a data cube,
+it may produce an `Aggregate` resource containing a tree like below:
+
+```
+Root aggregate
+├─ All coverages with same sample dimensions #1
+│  └─ ...
+└─ All coverages with same sample dimensions #2
+   ├─ Coverages with equivalent reference systems #1
+   │  └─ ...
+   └─ Coverages with equivalent reference systems #2
+      ├─ Slices with compatible "grid to CRS" #1
+      ├─ Slices with compatible "grid to CRS" #2
+      └─ ...</pre>
+```
+
+A future Apache SIS version will provide methods for controlling the way to aggregate
+such heterogeneous data set.
+
+This example works with `Resource` instances, which are not necessarily data loaded in memory.
+Consequently the `DataStore` instances must be kept open for all the duration of data cube usage.
+
+
+# Direct dependencies
+
+Maven coordinates                   | Module info                     | Remarks
+----------------------------------- | ------------------------------- | --------------------
+`org.apache.sis.code:sis-feature`   | `org.apache.sis.feature`        |
+`org.apache.sis.storage:sis-netcdf` | `org.apache.sis.storage.netcdf` |
+`edu.ucar:cdm-core`                 |                                 | For netCDF-4 or HDF5
+
+The `cdm-core` dependency can be omitted for netCDF-3 (a.k.a. "classic"),
+
+
+# Code example
+
+The file name and geospatial coordinates in following code need to be updated for yours data.
+
+{{< highlight java >}}
+import java.io.File;
+import org.apache.sis.storage.DataStore;
+import org.apache.sis.storage.DataStores;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.storage.aggregate.CoverageAggregator;
+
+public class DataLakeToDataCube {
+    /**
+     * Demo entry point.
+     *
+     * @param  args  ignored.
+     * @throws DataStoreException if an error occurred while reading the raster.
+     */
+    public static void main(String[] args) throws DataStoreException {
+        try (DataStore s1 = DataStores.open(new File("CMEMS_20220301.nc"));
+             DataStore s2 = DataStores.open(new File("CMEMS_20220302.nc"));
+             DataStore s3 = DataStores.open(new File("CMEMS_20220303.nc")))
+        {
+            /*
+             * Following casts are okay if we know that the resources are raster data,
+             * all of them having the same grid geometry. Otherwise the code can still
+             * work but would require more `if (x instanceof Y)` checks.
+             */
+            var r1 = (GridCoverageResource) s1.findResource("sea_surface_height_above_geoid");
+            var r2 = (GridCoverageResource) s2.findResource("sea_surface_height_above_geoid");
+            var r3 = (GridCoverageResource) s3.findResource("sea_surface_height_above_geoid");
+
+            System.out.printf("Extent of first set of slices:%n%s%n",  r1.getGridGeometry().getExtent());
+            System.out.printf("Extent of second set of slices:%n%s%n", r2.getGridGeometry().getExtent());
+            System.out.printf("Extent of third set of slices:%n%s%n",  r3.getGridGeometry().getExtent());
+
+            var aggregator = new CoverageAggregator(null);
+            aggregator.add(r1);
+            aggregator.add(r2);
+            aggregator.add(r3);
+            var dataCube = (GridCoverageResource) aggregator.build();
+            /*
+             * From this point, the data cube can be used as a three-dimension grid coverage.
+             * See "Get raster values at geographic (or pixel) coordinates" for usage examples.
+             * However all usages must be done inside this `try` block.
+             */
+            System.out.printf("Extent of the data cube:%n%s%n", dataCube.getGridGeometry().getExtent());
+        }
+    }
+}
+{{< / highlight >}}
+
+
+# Output
+
+The output depends on the raster data and the locale.
+Below is an example:
+
+```
+Extent of first set of slices:
+Column: [0 …  864]  (865 cells)
+Row:    [0 … 1080] (1081 cells)
+Time:   [0 …   95]   (96 cells)
+
+Extent of second set of slices:
+Column: [0 …  864]  (865 cells)
+Row:    [0 … 1080] (1081 cells)
+Time:   [0 …   95]   (96 cells)
+
+Extent of third set of slices:
+Column: [0 …  864]  (865 cells)
+Row:    [0 … 1080] (1081 cells)
+Time:   [0 …   95]   (96 cells)
+
+Extent of the data cube:
+Column: [0 …  864]  (865 cells)
+Row:    [0 … 1080] (1081 cells)
+Time:   [0 …  287]  (288 cells)
+```
diff --git a/content/howto/parallel_computation.md b/content/howto/parallel_computation.md
new file mode 100644
index 00000000..1de672a1
--- /dev/null
+++ b/content/howto/parallel_computation.md
@@ -0,0 +1,60 @@
+---
+title: Parallel computation
+---
+
+Some grid coverages will read or compute chunks of data only when first requested.
+For example when a coverage is the [result of a reprojection](resample_raster.html),
+or when a big coverage [uses deferred tile reading](rasters_bigger_than_memory.html).
+However if tiles are always requested in the same thread,
+it will result in a sequential, mono-threaded computation.
+Furthermore it may cause a lot of seek or "HTTP range" operations if tiles are read in random order.
+For parallel computation using all available processors,
+or for more efficient read operations,
+we need to inform Apache SIS in advance about which pixels are about to be requested.
+
+
+# Direct dependencies
+
+Maven coordinates                 | Module info              | Remarks
+--------------------------------- | ------------------------ | -------
+`org.apache.sis.code:sis-feature` | `org.apache.sis.feature` |
+
+
+# Code example
+
+{{< highlight java >}}
+import java.awt.image.ImagingOpException;
+import java.awt.image.RenderedImage;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.image.ImageProcessor;
+
+public class ParallelTileComputation {
+    /**
+     * Demo entry point.
+     *
+     * @param  args  ignored.
+     * @throws ImagingOpException unchecked exception thrown if an error occurred while computing a tile.
+     */
+    public static void main(String[] args) {
+        GridCoverage coverage = ...;        // See "Resample a raster" code example.
+        /*
+         * Get all data from the coverage, assuming that the grid is two-dimensional.
+         * If there is three or more dimensions, the null value needs to be replaced
+         * by a `GridExtent` specifying the two-dimensional slice to fetch.
+         */
+        RenderedImage data = coverage.render(null);
+        /*
+         * With above `RenderedImage`, tiles are computed when first requested and cached for future uses.
+         * If all tiles will be requested in the same thread, it results in a sequential tile computation.
+         * For parallel computation using all available processors, we need to inform Apache SIS in advance
+         * about which pixels will be requested. The `null` argument below means "all pixels in the image".
+         * Don't use that null argument if the image is very big!
+         */
+        var processor = new ImageProcessor();
+        data = processor.prefetch(data, null);
+        /*
+         * See for example "Get raster values at pixel coordinates" for using this image.
+         */
+    }
+}
+{{< / highlight >}}
diff --git a/content/howto/raster_values_at_pixel_coordinates.md b/content/howto/raster_values_at_pixel_coordinates.md
index b77a9143..d73968ed 100644
--- a/content/howto/raster_values_at_pixel_coordinates.md
+++ b/content/howto/raster_values_at_pixel_coordinates.md
@@ -14,6 +14,11 @@ but provide a _transfer function_ for converting those integers to "real world"
 Apache SIS can provide either the original integers or the converted values, at user's choice.
 This choice is specified by the boolean argument in the `data.​forConvertedValues(…)` call.
 
+Note that pixel coordinates are relative to the request made in the call to `render(…)`.
+They are not directly the grid coordinates of the coverage.
+The use of relative coordinates makes possible to avoid 32 bits integer overflow,
+and is also convenient for working on an area of interest regardless the grid coverage origin.
+
 
 # Direct dependencies
 
@@ -74,7 +79,7 @@ public class RasterValuesAtPixelCoordinates {
             System.out.printf("Value at (%d,%d) is %g %s.%n", pos.x, pos.y, value, unit);
             if (--n == 0) break;
         }
-        pit.moveTo(100, 200);
+        pit.moveTo(100, 200);                   // Relative to `extent` low coordinates.
         float value = pit.getSampleFloat(band);
         System.out.printf("Value at (100,200) is %g %s.%n", value, unit);
     }
diff --git a/content/howto/read_netcdf.md b/content/howto/read_netcdf.md
index 691b51db..d30150c0 100644
--- a/content/howto/read_netcdf.md
+++ b/content/howto/read_netcdf.md
@@ -61,7 +61,7 @@ public class ReadNetCDF {
      */
     public static GridCoverage example() throws DataStoreException {
         GridCoverage data;
-        try (DataStore store = DataStores.open(new File("CMEMS_R20220516.nc"))) {
+        try (DataStore store = DataStores.open(new File("CMEMS_20220516.nc"))) {
             /*
              * See what is inside this file. One of the components listed
              * below can be given in argument to `findResource(String)`.
diff --git a/content/howto/resample_raster.md b/content/howto/resample_raster.md
index 9c322953..8e498b1f 100644
--- a/content/howto/resample_raster.md
+++ b/content/howto/resample_raster.md
@@ -28,7 +28,6 @@ for example using Well Known Text (WKT) format.
 The file name in following code need to be updated for yours data.
 
 {{< highlight java >}}
-import java.awt.image.ImagingOpException;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridCoverageProcessor;
 import org.apache.sis.image.Interpolation;
@@ -43,7 +42,6 @@ public class ResampleRaster {
      * @param  args  ignored.
      * @throws FactoryException   if an error occurred while creating the Coordinate Reference System (CRS).
      * @throws TransformException if an error occurred while transforming coordinates to the target CRS.
-     * @throws ImagingOpException unchecked exception thrown if an error occurred while resampling a tile.
      */
     public static void main(String[] args) throws FactoryException, TransformException {
         GridCoverage data = ...;      // See "Read netCDF" or "Read GeoTIFF" code examples.


[sis-site] 01/03: Clarify in which conditions the `GridCoverage` can be used outside the `try` block.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/sis-site.git

commit eb53c5465565ad388339adf1e8a5adbcdb0f2a71
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Apr 18 12:19:12 2023 +0200

    Clarify in which conditions the `GridCoverage` can be used outside the `try` block.
---
 content/howto/rasters_bigger_than_memory.md |  8 ++++++++
 content/howto/read_geotiff.md               | 19 ++++++++++++++++++-
 content/howto/read_netcdf.md                | 22 +++++++++++++++++++++-
 3 files changed, 47 insertions(+), 2 deletions(-)

diff --git a/content/howto/rasters_bigger_than_memory.md b/content/howto/rasters_bigger_than_memory.md
index 09f46d29..e4a2451f 100644
--- a/content/howto/rasters_bigger_than_memory.md
+++ b/content/howto/rasters_bigger_than_memory.md
@@ -11,6 +11,10 @@ It integrates well with operations provided by Apache {{% SIS %}} such as
 [raster resampling](resample_raster.html) and
 [getting values at geographic coordinates](raster_values_at_geographic_coordinates.html).
 
+The approach demonstrated in this example has one drawback compared to the default behavior:
+the `DataStore` must be kept open during all the time that the `GridCoverage` is used.
+Consequently the `data` variable should not be used outside the `try` block in this example.
+
 The example in this page works with pixel coordinates.
 For working with geographic coordinates, see
 [values at geographic coordinates](raster_values_at_geographic_coordinates.html) code example.
@@ -70,6 +74,10 @@ public class RasterBiggerThanMemory {
             firstImage.setLoadingStrategy(RasterLoadingStrategy.AT_GET_TILE_TIME);
             GridCoverage data = firstImage.read(null, null);
             printPixelValue(data, false);
+            /*
+             * Contrarily to other examples, the `GridCoverage` fetched in deferred reading mode
+             * can NOT be used outside this `try` block, because the `DataStore` must be open.
+             */
         }
     }
 
diff --git a/content/howto/read_geotiff.md b/content/howto/read_geotiff.md
index 1f37c984..6bfe43aa 100644
--- a/content/howto/read_geotiff.md
+++ b/content/howto/read_geotiff.md
@@ -53,6 +53,17 @@ public class ReadGeoTIFF {
      * @throws ImagingOpException unchecked exception thrown if an error occurred while loading a tile.
      */
     public static void main(String[] args) throws DataStoreException {
+        example();
+    }
+
+    /**
+     * Reads an example file and prints some information about it.
+     *
+     * @return the raster data.
+     * @throws DataStoreException if an error occurred while reading the raster.
+     */
+    public static GridCoverage example() throws DataStoreException {
+        GridCoverage data;
         try (DataStore store = DataStores.open(new File("Aéroport.tiff"))) {
             /*
              * This data store is an aggregate because a GeoTIFF file may contain many images.
@@ -64,7 +75,7 @@ public class ReadGeoTIFF {
             /*
              * Read the resource immediately and fully.
              */
-            GridCoverage data = firstImage.read(null, null);
+            data = firstImage.read(null, null);
             System.out.printf("Information about the selected image:%n%s%n", data);
             /*
              * Read only a subset of the resource. The Area Of Interest can be specified
@@ -79,6 +90,12 @@ public class ReadGeoTIFF {
             System.out.printf("Information about the resource subset:%n%s%n",
                               data.getGridGeometry().getExtent());
         }
+        /*
+         * By default, it is possible to continue to use the `GridCoverage` (but not the `Resource`) after
+         * the `DataStore` has been closed because data are in memory. Note that it would not be the case
+         * if deferred data loading was enabled has shown in "Handle rasters bigger than memory" example.
+         */
+        return data;
     }
 }
 {{< / highlight >}}
diff --git a/content/howto/read_netcdf.md b/content/howto/read_netcdf.md
index f10ae258..691b51db 100644
--- a/content/howto/read_netcdf.md
+++ b/content/howto/read_netcdf.md
@@ -50,6 +50,17 @@ public class ReadNetCDF {
      * @throws DataStoreException if an error occurred while reading the raster.
      */
     public static void main(String[] args) throws DataStoreException {
+        example();
+    }
+
+    /**
+     * Reads an example file and prints some information about it.
+     *
+     * @return the raster data.
+     * @throws DataStoreException if an error occurred while reading the raster.
+     */
+    public static GridCoverage example() throws DataStoreException {
+        GridCoverage data;
         try (DataStore store = DataStores.open(new File("CMEMS_R20220516.nc"))) {
             /*
              * See what is inside this file. One of the components listed
@@ -61,8 +72,9 @@ public class ReadNetCDF {
             if (resource instanceof GridCoverageResource gridded) {
                 /*
                  * Read the resource immediately and fully.
+                 * `data` can be used outside the `try` block.
                  */
-                GridCoverage data = gridded.read(null, null);
+                data = gridded.read(null, null);
                 System.out.printf("Information about the selected resource:%n%s%n", data);
                 /*
                  * Read only a subset of the resource. The Area Of Interest can be specified
@@ -75,8 +87,16 @@ public class ReadNetCDF {
                 data = gridded.read(new GridGeometry(null, areaOfInterest, GridOrientation.HOMOTHETY), null);
                 System.out.printf("Information about the resource subset:%n%s%n",
                                   data.getGridGeometry().getExtent());
+            } else {
+                throw new DataStoreException("Unexpected type of resource.");
             }
         }
+        /*
+         * By default, it is possible to continue to use the `GridCoverage` (but not the `Resource`) after
+         * the `DataStore` has been closed because data are in memory. Note that it would not be the case
+         * if deferred data loading was enabled has shown in "Handle rasters bigger than memory" example.
+         */
+        return data;
     }
 
     /**