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
The following commit(s) were added to refs/heads/main by this push: new 9845d209 Add a "How to…" section with three first items. 9845d209 is described below commit 9845d209a66ca0c431ca1de075e1396f2139823c Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Wed Jan 18 11:09:36 2023 +0100 Add a "How to…" section with three first items. --- content/howto/_index.md | 11 ++ .../raster_values_at_geographic_coordinates.md | 185 +++++++++++++++++++++ content/howto/rasters_bigger_than_memory.md | 133 +++++++++++++++ content/howto/resample_and_save_raster.md | 147 ++++++++++++++++ layouts/_default/baseof.html | 1 + layouts/partials/menu.html | 1 + 6 files changed, 478 insertions(+) diff --git a/content/howto/_index.md b/content/howto/_index.md new file mode 100644 index 00000000..ba3671f0 --- /dev/null +++ b/content/howto/_index.md @@ -0,0 +1,11 @@ +--- +title: How to +--- + +Java code examples for performing some tasks with Apache {{% SIS %}}. + +# Rasters + +* [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 and write to a file](howto/resample_and_save_raster.html) diff --git a/content/howto/raster_values_at_geographic_coordinates.md b/content/howto/raster_values_at_geographic_coordinates.md new file mode 100644 index 00000000..334f075d --- /dev/null +++ b/content/howto/raster_values_at_geographic_coordinates.md @@ -0,0 +1,185 @@ +--- +title: Get raster values at geographic coordinates +--- + +This example reads a netCDF file and fetches values at given coordinates. +The coordinates can be expressed in different Coordinate Reference System (CRS). +Conversions from geographic coordinates to pixel coordinates, +followed by conversions from raster data to units of measurement, +are done automatically. +Raster rata and spatiotemporal coordinates can have more than two dimensions. + +This example uses data in netCDF format. +A netCDF file can contain an arbitrary amount of variables. +For this reason, the data store implements the `Aggregate` interface +and the desired variable must be specified. +A similar code can be used for reading data in other +formats supported by Apache {{% SIS %}} such as GeoTIFF, +but not all formats are aggregates. +For some file formats, the data store implements directly +the `GridCoverageResource` interface instead of `Aggregate`. + + +# Direct dependencies + +Maven coordinates | Module info | Remarks +----------------------------------- | ------------------------------- | -------------------- +`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"), +GeoTIFF or any other [formats supported by Apache SIS](../formats.html). +For the dependencies required for reading GeoTIFF instead of netCDF files, +see the [rasters bigger than memory](rasters_bigger_than_memory.html) snippet. + + +# Code snippet + +The file name, resource name and geographic coordinates +in following snippet need to be updated for yours data. + +{{< highlight java >}} +import java.io.File; +import java.util.Map; +import javax.measure.Unit; +import org.apache.sis.storage.Resource; +import org.apache.sis.storage.Aggregate; +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.coverage.grid.GridCoverage; +import org.apache.sis.geometry.GeneralDirectPosition; +import org.apache.sis.referencing.CommonCRS; +import org.apache.sis.measure.Units; + +public class RasterValuesAtGeographicCoordinates { + /** + * 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 store = DataStores.open(new File("CMEMS.nc"))) { + /* + * See what is inside this file. One of the components listed + * below can be given in argument to `findResource(String)`. + */ + printComponents(store); + /* + * The following code read fully the specified resource. + * For reading only a subset, or for handling data bigger + * than memory, see "How to..." in Apache SIS web site. + */ + Resource resource = store.findResource("sea_surface_height_above_geoid"); + GridCoverage data = ((GridCoverageResource) resource).read(null, null); + System.out.printf("Information about the selected resource:%n%s%n", data); + /* + * Switch to a view of the data in the units of measurement. + * Then get the unit of measurement of the first band (0). + * If no unit is specified, fallback on dimensionless unit. + */ + data = data.forConvertedValues(true); + int band = 0; + Unit<?> unit = data.getSampleDimensions().get(band).getUnits().orElse(Units.UNITY); + /* + * Get raster values at geographic coordinates expressed in WGS84. + * Coordinate values in this example are in (latitude, longitude) order. + * Any compatible coordinate reference system (CRS) can be used below, + * Apache SIS will automatically transform to the CRS used by the raster. + * If the raster data are three-dimensional, a 3D CRS should be specified. + */ + System.out.println("Evaluation at some (latitude, longitude) coordinates:"); + var point = new GeneralDirectPosition(CommonCRS.WGS84.geographic()); + GridCoverage.Evaluator eval = data.evaluator(); + /* + * If the data are three-dimensional but we still want to use two-dimensional + * coordinates, we need to specify a default value for the temporal dimension. + * This code set the default to slice 0 (the first slice) in dimension 2. + * Omit this line if the data are two-dimensional or if `point` has a 3D CRS. + */ + eval.setDefaultSlice(Map.of(2, 0L)); + /* + * The same `Evaluator` can be reused as often as needed for evaluating + * at many points. + */ + point.setCoordinate(40, -10); // 40°N 10°W + double[] values = eval.apply(point); + System.out.printf("- Value at %s is %g %s.%n", point, values[band], unit); + + point.setCoordinate(30, -15); // 30°N 15°W + values = eval.apply(point); + System.out.printf("- Value at %s is %g %s.%n", point, values[band], unit); + } + } + + /** + * Lists the components found in the given data store. + * They are the values that can be given to {@link DataStore#findResource(String). + * + * @param store the data store from which to get the components. + * @throws DataStoreException if an error occurred while reading the raster. + */ + private static void printComponents(DataStore store) throws DataStoreException { + if (store instanceof Aggregate a) { + System.out.println("Components found in the data store:"); + for (Resource component : a.components()) { + component.getIdentifier().ifPresent((id) -> System.out.println("- " + id)); + } + } else { + System.out.println("The data store is not an aggregate."); + } + System.out.println(); + } +} +{{< / highlight >}} + + +# Output + +The output depends on the raster data and the locale. +Below is an example: + +``` +Components found in the data store: +- sea_surface_height_above_geoid +- sea_water_velocity + +Information about the selected resource: +Raster + ├─Coverage domain + │ ├─Grid extent + │ │ ├─Column: [0 … 864] (865 cells) + │ │ ├─Row: [0 … 1080] (1081 cells) + │ │ └─Time: [0 … 95] (96 cells) + │ ├─Geographic extent + │ │ ├─Lower bound: 25°59′09″N 19°00′50″W 2022-05-16T00:00:00Z + │ │ └─Upper bound: 56°00′50″N 05°00′50″E 2022-05-17T00:00:00Z + │ ├─Envelope + │ │ ├─Geodetic longitude: -19.01388888888889 … 5.013888888888888 ∆Lon = 0.02777778° + │ │ ├─Geodetic latitude: 25.98611111111111 … 56.013888888888886 ∆Lat = 0.02777778° + │ │ └─time: 634,392.0 … 634,416.0 ∆t = 0.25 h + │ ├─Coordinate reference system + │ │ └─time latitude longitude + │ └─Conversion (origin in a cell center) + │ └─┌ ┐ + │ │ 0.027777777777777776 0 0 -19.000 │ + │ │ 0 0.027777777777777776 0 26.000 │ + │ │ 0 0 0.25 634392.125 │ + │ │ 0 0 0 1 │ + │ └ ┘ + └─Sample dimensions + └─┌────────────────────┬────────────────────────┬────────────────────┐ + │ Values │ Measures │ Name │ + ╞════════════════════╧════════════════════════╧════════════════════╡ + │ zos │ + ├────────────────────┬────────────────────────┬────────────────────┤ + │ -32,767 │ NaN #0 │ Fill value │ + │ [-10,000 … 10,000] │ [-10.0000 … 10.0000] m │ Sea surface height │ + └────────────────────┴────────────────────────┴────────────────────┘ + +Evaluation at some (latitude, longitude) coordinates: +- Value at POINT(40 -10) is 0.188000 m. +- Value at POINT(30 -15) is 0.619000 m. +``` diff --git a/content/howto/rasters_bigger_than_memory.md b/content/howto/rasters_bigger_than_memory.md new file mode 100644 index 00000000..c6d89544 --- /dev/null +++ b/content/howto/rasters_bigger_than_memory.md @@ -0,0 +1,133 @@ +--- +title: Handle rasters bigger than memory +--- + +This example opens a big GeoTIFF file without reading the tiles immediately. +Instead, tiles will be read only when requested by a call to the Java2D `RenderedImage.getTile(int, int)` method. +Loaded tiles are cached by soft references, i.e. they may be discarted and reloaded when needed again. +This approach allows processing of raster data larger than memory, +provided that the application does not request all tiles at once. +It integrates well with operations provided by Apache {{% SIS %}} such as +[raster resampling](resample_and_save_raster.html) and +[getting values at geographic coordinates](raster_values_at_geographic_coordinates.html). + +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) snippet. + + +# Direct dependencies + +Maven coordinates | Module info | Remarks +------------------------------------------- | ------------------------------------- | ----------------------------- +`org.apache.sis.storage:sis-geotiff` | `org.apache.sis.storage.geotiff` | +`org.apache.sis.non-free:sis-embedded-data` | `org.apache.sis.referencing.database` | Optional. Non-Apache license. + +The [EPSG dependency](../epsg.html) may or may not be needed, +depending how the Coordinate Reference System (CRS) is encoded in the GeoTIFF file. + + +# Code snippet + +The file name in following snippet need to be updated for yours data. + +{{< highlight java >}} +import java.io.File; +import java.util.Collection; +import java.awt.Rectangle; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.ImagingOpException; +import org.apache.sis.image.ImageProcessor; +import org.apache.sis.storage.Resource; +import org.apache.sis.storage.Aggregate; +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.RasterLoadingStrategy; +import org.apache.sis.coverage.grid.GridCoverage; + +public class RasterBiggerThanMemory { + /** + * Demo entry point. + * + * @param args ignored. + * @throws DataStoreException if an error occurred while reading the raster. + * @throws ImagingOpException unchecked exception thrown if an error occurred while loading a tile. + */ + public static void main(String[] args) throws DataStoreException { + try (DataStore store = DataStores.open(new File("TM250m.tiff"))) { + /* + * This data store is an aggregate because a GeoTIFF file may contain many images. + * Not all data stores are aggregate, so the following casts do not apply to all. + * For this example, we know that the file is GeoTIFF and we take the first image. + */ + Collection<? extends Resource> allImages = ((Aggregate) store).components(); + GridCoverageResource firstImage = (GridCoverageResource) allImages.iterator().next(); + /* + * Following line requests to load data at `RenderedImage.getTile(…)` invocation time. + * This is the key line of code for handling rasters larger than memory, but is effective + * only if the file is tiled as in, for example, Cloud Optimized GeoTIFF (COG) convention. + * Without this line, the default is to load all data at `GridCoverageResource.read(…)` + * invocation time. + */ + firstImage.setLoadingStrategy(RasterLoadingStrategy.AT_GET_TILE_TIME); + GridCoverage data = firstImage.read(null, null); + System.out.printf("Information about the selected image:%n%s%n", data); + /* + * Get an arbitrary tile, then get an arbitrary sample value in an arbitrary band + * (the blue channel) of that tile. + */ + RenderedImage image = data.render(null); + System.out.printf("The image has %d × %d tiles.%n", image.getNumXTiles(), image.getNumYTiles()); + + Raster tile = image.getTile(130, 80); // This is where tile loading actually happen. + System.out.printf("Got a tile starting at coordinates %d, %d.%n", tile.getMinX(), tile.getMinY()); + System.out.printf("A sample value in a tile: %d%n", tile.getSample(93710, 57680, 2)); + /* + * If we know in advance which tiles will be requested, specifying them in advance allows + * the GeoTIFF reader to use a better strategy than loading the tiles in random order. + */ + var processor = new ImageProcessor(); + image = processor.prefetch(image, new Rectangle(90000, 50000, 1000, 1000)); + tile = image.getTile(130, 80); + System.out.printf("Same, but from prefetched image: %d%n%n", tile.getSample(93710, 57680, 2)); + } + } +} +{{< / highlight >}} + + +# Output + +The output depends on the raster data and the locale. +Below is an example: + +``` +Information about the selected image: +CompressedSubset + └─Coverage domain + ├─Grid extent + │ ├─Column: [0 … 172799] (172800 cells) + │ └─Row: [0 … 86399] (86400 cells) + ├─Geographic extent + │ ├─Lower bound: 90°00′00″S 180°00′00″W + │ └─Upper bound: 90°00′00″N 180°00′00″E + ├─Envelope + │ ├─Geodetic longitude: -180.000 … 180.000 ∆Lon = 0.00208333° + │ └─Geodetic latitude: -90.000 … 90.000 ∆Lat = 0.00208333° + ├─Coordinate reference system + │ └─CRS:84 — WGS 84 + └─Conversion (origin in a cell center) + └─┌ ┐ + │ 0.0020833333333333333 0 -179.99895833333332 │ + │ 0 -0.0020833333333333333 89.99895833333333 │ + │ 0 0 1 │ + └ ┘ + +The image has 240 × 120 tiles. +Got a tile starting at coordinates 93600, 57600. +A sample value in a tile: 20 +Same, but from prefetched image: 20 +``` diff --git a/content/howto/resample_and_save_raster.md b/content/howto/resample_and_save_raster.md new file mode 100644 index 00000000..039299ff --- /dev/null +++ b/content/howto/resample_and_save_raster.md @@ -0,0 +1,147 @@ +--- +title: Resample a raster and write to a file +--- + +This example reads a raster in a GeoTIFF file +and reprojects it to a different Coordinate Reference System (CRS). +The result is saved as a World File in PNG format. + + +# Direct dependencies + +Maven coordinates | Module info | Remarks +------------------------------------------- | ------------------------------------- | ----------------------------- +`org.apache.sis.storage:sis-geotiff` | `org.apache.sis.storage.geotiff` | +`org.apache.sis.non-free:sis-embedded-data` | `org.apache.sis.referencing.database` | Non-Apache license. + +The [EPSG dependency](../epsg.html) is necessary for this example +because a Coordinate Reference System (CRS) is instantiated from its EPSG code. +But it would also be possible to specify a CRS without EPSG code, +for example using Well Known Text (WKT) format. + + +# Code snippet + +The file name in following snippet need to be updated for yours data. + +{{< highlight java >}} +import java.nio.file.Paths; +import java.util.Collection; +import java.awt.image.ImagingOpException; +import org.apache.sis.storage.Resource; +import org.apache.sis.storage.Aggregate; +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.coverage.grid.GridCoverage; +import org.apache.sis.coverage.grid.GridCoverageProcessor; +import org.apache.sis.image.Interpolation; +import org.apache.sis.referencing.CRS; +import org.opengis.referencing.operation.TransformException; +import org.opengis.util.FactoryException; + +public class ResampleAndSaveRaster { + /** + * Demo entry point. + * + * @param args ignored. + * @throws DataStoreException if an error occurred while reading the raster. + * @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 DataStoreException, FactoryException, TransformException { + try (DataStore store = DataStores.open(Paths.get("Airport.tiff"))) { + /* + * This data store is an aggregate because a GeoTIFF file may contain many images. + * Not all data stores are aggregate, so the following casts do not apply to all. + * For this example, we know that the file is GeoTIFF and we take the first image. + */ + Collection<? extends Resource> allImages = ((Aggregate) store).components(); + GridCoverageResource firstImage = (GridCoverageResource) allImages.iterator().next(); + /* + * The following code read fully the specified resource. + * For reading only a subset, or for handling data bigger + * than memory, see "How to..." in Apache SIS web site. + */ + GridCoverage data = firstImage.read(null, null); + System.out.printf("Information about the selected image:%n%s%n", data); + /* + * Reproject to "WGS 84 / World Mercator" (EPSG::3395) using bilinear interpolation. + * This example lets Apache SIS choose the output grid size and resolution. + * But it is possible to specify those aspects if desired. + */ + var processor = new GridCoverageProcessor(); + processor.setInterpolation(Interpolation.BILINEAR); + data = processor.resample(data, CRS.forCode("EPSG::3395")); + System.out.printf("Information about the image after reprojection:%n%s%n", data); + /* + * TODO: Apache SIS is missing an `DataStores.write(…)` convenience method. + * Writing a TIFF World File is possible but requires use of internal API. + * A public convenience method will be added in next version. + */ + } + } +} +{{< / highlight >}} + + +# Output + +The output depends on the raster data and the locale. +Below is an example: + +``` +Information about the image after reprojection: +GridCoverage2D + ├─Coverage domain + │ ├─Grid extent + │ │ ├─Column: [0 … 8191] (8192 cells) + │ │ └─Row: [0 … 8191] (8192 cells) + │ ├─Geographic extent + │ │ ├─Lower bound: 48°59′20″N 02°31′33″E + │ │ └─Upper bound: 49°01′08″N 02°34′16″E + │ ├─Envelope + │ │ ├─Easting: 465,341.6 … 468,618.39999999997 ∆E = 0.4 m + │ │ └─Northing: 5,426,352.8 … 5,429,629.6 ∆N = 0.4 m + │ ├─Coordinate reference system + │ │ └─EPSG:32631 — WGS 84 / UTM zone 31N + │ └─Conversion (origin in a cell center) + │ └─┌ ┐ + │ │ 0.4 0 465341.8 │ + │ │ 0 -0.4 5429629.4 │ + │ │ 0 0 1 │ + │ └ ┘ + └─Image layout + ├─Origin: 0, 0 + ├─Tile size: 8,192 × 128 + ├─Data type: byte + └─Image is opaque. + +Information about the image after reprojection: +GridCoverage2D + ├─Coverage domain + │ ├─Grid extent + │ │ ├─Dimension 0: [0 … 8239] (8240 cells) + │ │ └─Dimension 1: [0 … 8240] (8241 cells) + │ ├─Geographic extent + │ │ ├─Lower bound: 48°59′20″N 02°31′33″E + │ │ └─Upper bound: 49°01′08″N 02°34′16″E + │ ├─Envelope + │ │ ├─Easting: 281,190.4273301751 … 286,207.11249780044 ∆E = 0.60882102 m + │ │ └─Northing: 6,240,752.860382801 … 6,245,770.154371441 ∆N = 0.60882102 m + │ ├─Coordinate reference system + │ │ └─EPSG:3395 — WGS 84 / World Mercator + │ └─Conversion (origin in a cell center) + │ └─┌ ┐ + │ │ 0.6088210154885099 0 281190.73174068285 │ + │ │ 0 -0.60882101548851 6245769.8499609330 │ + │ │ 0 0 1 │ + │ └ ┘ + └─Image layout + ├─Origin: 0, 0 + ├─Tile size: 1,648 × 201 + ├─Data type: byte + └─Image is opaque. +``` diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index c3b8ed2d..06911070 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -37,6 +37,7 @@ <li><a class="nav-link {{ if eq .Page.RelPermalink "/" }} active {{ else }} text-white {{ end }}" href="/index.html">Home</a></li> <li><a class="nav-link text-white" href="http://www.apache.org/licenses/">License</a></li> <li><a class="nav-link {{ if eq .Page.RelPermalink "/downloads.html" }} active {{ else }} text-white {{ end }}" href="/downloads.html">Downloads</a></li> + <li><a class="nav-link {{ if strings.Contains .Page.RelPermalink "/howto" }} active {{ else }} text-white {{ end }}" href="/howto.html">How to…</a></li> <li><a class="nav-link {{ if eq .Page.RelPermalink "/standards.html" }} active {{ else }} text-white {{ end }}" href="/standards.html">Standards</a></li> <li><a class="nav-link {{ if eq .Page.RelPermalink "/formats.html" }} active {{ else }} text-white {{ end }}" href="/formats.html">Data formats</a></li> <li><a class="nav-link {{ if eq .Page.RelPermalink "/epsg.html" }} active {{ else }} text-white {{ end }}" href="/epsg.html">EPSG Database</a></li> diff --git a/layouts/partials/menu.html b/layouts/partials/menu.html index e3f52afe..14337b90 100644 --- a/layouts/partials/menu.html +++ b/layouts/partials/menu.html @@ -24,6 +24,7 @@ <ul class="dropdown-menu" aria-labelledby="menuDocumentation"> <li><a class="dropdown-item" href="/apidocs/index.html">Online Javadoc</a></li> <li><a class="dropdown-item" href="/book/en/developer-guide.html">Developer Guide</a></li> + <li><a class="dropdown-item" href="/howto.html">How to…</a></li> <li><a class="dropdown-item" href="/formats.html">Supported formats</a></li> <li><a class="dropdown-item" href="/tables/CoordinateReferenceSystems.html">Supported CRS</a></li> <li><a class="dropdown-item" href="/tables/CoordinateOperationMethods.html">Map Projections</a></li>