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 3a0e8307 Add more "How to" items. 3a0e8307 is described below commit 3a0e8307869a9fb4c638bb140ec6f27af6cf461a Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sat Jan 21 14:16:36 2023 +0100 Add more "How to" items. --- content/howto/_index.md | 14 +- content/howto/envelopes_in_different_crs.md | 65 ++++ content/howto/export_metadata_to_xml.md | 338 +++++++++++++++++++++ content/howto/geographic_bounding_box.md | 78 +++++ .../raster_values_at_geographic_coordinates.md | 4 +- content/howto/transform_envelopes.md | 105 +++++++ 6 files changed, 599 insertions(+), 5 deletions(-) diff --git a/content/howto/_index.md b/content/howto/_index.md index a7801e8e..0f650fbd 100644 --- a/content/howto/_index.md +++ b/content/howto/_index.md @@ -12,9 +12,17 @@ The examples are grouped in the following sections: * [Instantiate a Universal Transverse Mercator (UTM) projection](howto/instantiate_utm_projection.html) * [Instantiate a Pseudo Mercator (a.k.a. Google) projection](faq.html#google) -* [Transform coordinates between two reference systems](howto/transform_coordinates.html) -* [Get the EPSG code or URN of an existing {{% CRS %}}](howto/lookup_crs_urn.html) -* [Determine if two {{% CRS %}} are functionally equal](howto/crs_equality.html) +* [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) +* [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) + + +# Metadata {#metadata} + +* [Get the geographic bounding box of a data file](howto/geographic_bounding_box.html) +* [Export metadata of a data file to standard XML](howto/export_metadata_to_xml.html) # Grid coverages (rasters) {#raster} diff --git a/content/howto/envelopes_in_different_crs.md b/content/howto/envelopes_in_different_crs.md new file mode 100644 index 00000000..75b15604 --- /dev/null +++ b/content/howto/envelopes_in_different_crs.md @@ -0,0 +1,65 @@ +--- +title: Union or intersection of envelopes in different CRS +--- + +Before to compute the union or intersection of two or more envelopes (bounding boxes), +all envelopes must be [transformed](transform_envelopes.html) to the same Coordinate Reference System (CRS). +But the choice of a common {{% CRS %}} is not easy. +We must verify that all envelopes are inside the domain of validity of the common {{% CRS %}}, +which may require to choose a common {{% CRS %}} different than the {{% CRS %}} of all envelopes. +Apache {{% SIS %}} can handle this task automatically. + + +# Direct dependencies + +Maven coordinates | Module info +------------------------------------------- | ---------------------------- +`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.opengis.geometry.Envelope; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.TransformException; +import org.opengis.util.FactoryException; +import org.apache.sis.referencing.CommonCRS; +import org.apache.sis.geometry.Envelopes; +import org.apache.sis.geometry.Envelope2D; + +public class UnionOfEnvelopes { + /** + * Demo entry point. + * + * @param args ignored. + * @throws FactoryException if an error occurred while creating a Coordinate Reference System (CRS). + * @throws TransformException if an error occurred while transforming coordinates to the target CRS. + */ + public static void main(String[] args) throws FactoryException, TransformException { + CoordinateReferenceSystem crs1 = CommonCRS.WGS84.universal(40, 10); // 40°N 10°E + CoordinateReferenceSystem crs2 = CommonCRS.WGS84.universal(40, 20); // 40°N 20°E + + Envelope2D bbox1 = new Envelope2D(crs1, 500_000, 400_000, 100_000, 100_000); + Envelope2D bbox2 = new Envelope2D(crs2, 400_000, 500_000, 100_000, 100_000); + Envelope union = Envelopes.union(bbox1, bbox2); + + System.out.println("First CRS: " + crs1.getName()); + System.out.println("Second CRS: " + crs2.getName()); + System.out.println("Selected CRS: " + union.getCoordinateReferenceSystem().getName()); + System.out.println("Union result: " + union); + } +} +{{< / highlight >}} + + +# Output + +``` +First CRS: EPSG:WGS 84 / UTM zone 32N +Second CRS: EPSG:WGS 84 / UTM zone 34N +Selected CRS: EPSG:WGS 84 +Union result: BOX(3.6184285185271796 9, 5.428225419697392 21) +``` diff --git a/content/howto/export_metadata_to_xml.md b/content/howto/export_metadata_to_xml.md new file mode 100644 index 00000000..1e444eb9 --- /dev/null +++ b/content/howto/export_metadata_to_xml.md @@ -0,0 +1,338 @@ +--- +title: Geographic bounding box of a data file +--- + +This example prints the metadata of a netCDF file in the XML format +defined by the ISO 19115-3 international standard. +The coverage values are not read, only the netCDF file header is read. + + +# 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 [geographic bounding box](geographic_bounding_box.html) code example. + + +# Code example + +The file name in following code need to be updated for yours data. + +{{< highlight java >}} +import java.util.Map; +import java.io.File; +import java.io.StringWriter; +import javax.xml.bind.JAXBException; +import javax.xml.transform.stream.StreamResult; +import org.opengis.metadata.Metadata; +import org.apache.sis.storage.DataStore; +import org.apache.sis.storage.DataStores; +import org.apache.sis.storage.DataStoreException; +import org.apache.sis.xml.XML; + +public class ExportMetadata { + /** + * Demo entry point. + * + * @param args ignored. + * @throws DataStoreException if an error occurred while reading the data file. + * @throws JAXBException if an error occurred while marshalling metadata to XML. + */ + public static void main(String[] args) throws DataStoreException, JAXBException { + try (DataStore store = DataStores.open(new File("CMEMS.nc"))) { + Metadata metadata = store.getMetadata(); + System.out.println(XML.marshal(metadata)); + /* + * By default the XML schema is the most recent version of the standard supported + * by Apache SIS. But the legacy version published in 2007 is still in wide use. + * The legacy version can be requested with the `METADATA_VERSION` property. + */ + Map<String,String> config = Map.of(XML.METADATA_VERSION, "2007"); + StringWriter result = new StringWriter(); + XML.marshal(metadata, new StreamResult(result), config); + // Result is in `result.toString()`. + } + } +} +{{< / highlight >}} + + +# Output + +The output depends on the data and the locale. +Below is an example: + +{{< highlight xml >}} +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<mdb:MD_Metadata xmlns:cit="http://standards.iso.org/iso/19115/-3/cit/1.0" + xmlns:gco="http://standards.iso.org/iso/19115/-3/gco/1.0" + xmlns:mcc="http://standards.iso.org/iso/19115/-3/mcc/1.0" + xmlns:mdb="http://standards.iso.org/iso/19115/-3/mdb/1.0" + xmlns:mrc="http://standards.iso.org/iso/19115/-3/mrc/1.0" + xmlns:mrd="http://standards.iso.org/iso/19115/-3/mrd/1.0" + xmlns:mri="http://standards.iso.org/iso/19115/-3/mri/1.0" + xmlns:mrl="http://standards.iso.org/iso/19115/-3/mrl/1.0" + xmlns:mrs="http://standards.iso.org/iso/19115/-3/mrs/1.0" + xmlns:msr="http://standards.iso.org/iso/19115/-3/msr/1.0"> + <mdb:metadataStandard> + <!-- Omitted for brevity --> + </mdb:metadataStandard> + <mdb:spatialRepresentationInfo> + <msr:MD_GridSpatialRepresentation> + <msr:numberOfDimensions> + <gco:Integer>3</gco:Integer> + </msr:numberOfDimensions> + <msr:axisDimensionProperties> + <msr:MD_Dimension> + <msr:dimensionName> + <msr:MD_DimensionNameTypeCode codeList="(…snip…)#MD_DimensionNameTypeCode" codeListValue="column">Column</msr:MD_DimensionNameTypeCode> + </msr:dimensionName> + <msr:dimensionSize> + <gco:Integer>865</gco:Integer> + </msr:dimensionSize> + </msr:MD_Dimension> + </msr:axisDimensionProperties> + <msr:axisDimensionProperties> + <msr:MD_Dimension> + <msr:dimensionName> + <msr:MD_DimensionNameTypeCode codeList="(…snip…)#MD_DimensionNameTypeCode" codeListValue="row">Row</msr:MD_DimensionNameTypeCode> + </msr:dimensionName> + <msr:dimensionSize> + <gco:Integer>1081</gco:Integer> + </msr:dimensionSize> + </msr:MD_Dimension> + </msr:axisDimensionProperties> + <msr:axisDimensionProperties> + <msr:MD_Dimension> + <msr:dimensionName> + <msr:MD_DimensionNameTypeCode codeList="(…snip…)#MD_DimensionNameTypeCode" codeListValue="time">Time</msr:MD_DimensionNameTypeCode> + </msr:dimensionName> + <msr:dimensionSize> + <gco:Integer>96</gco:Integer> + </msr:dimensionSize> + </msr:MD_Dimension> + </msr:axisDimensionProperties> + <msr:cellGeometry> + <msr:MD_CellGeometryCode codeList="(…snip…)#MD_CellGeometryCode" codeListValue="area">Area</msr:MD_CellGeometryCode> + </msr:cellGeometry> + <msr:transformationParameterAvailability> + <gco:Boolean>false</gco:Boolean> + </msr:transformationParameterAvailability> + </msr:MD_GridSpatialRepresentation> + </mdb:spatialRepresentationInfo> + <mdb:referenceSystemInfo> + <mrs:MD_ReferenceSystem> + <mrs:referenceSystemIdentifier> + <mcc:MD_Identifier> + <mcc:code> + <gco:CharacterString>time latitude longitude</gco:CharacterString> + </mcc:code> + </mcc:MD_Identifier> + </mrs:referenceSystemIdentifier> + </mrs:MD_ReferenceSystem> + </mdb:referenceSystemInfo> + <mdb:identificationInfo> + <mri:MD_DataIdentification> + <mri:citation> + <cit:CI_Citation> + <cit:title> + <gco:CharacterString>Ocean surface 15-minutes mean fields for the Iberia-Biscay-Ireland (IBI) region</gco:CharacterString> + </cit:title> + <cit:identifier> + <mcc:MD_Identifier> + <mcc:code> + <gco:CharacterString>CMEMS_v5r1_IBI_PHY_NRT_PdE_15minav_20220516_20220516_R20220516_FC01</gco:CharacterString> + </mcc:code> + </mcc:MD_Identifier> + </cit:identifier> + <cit:citedResponsibleParty> + <cit:CI_Responsibility> + <cit:role> + <cit:CI_RoleCode codeList="(…snip…)#CI_RoleCode" codeListValue="originator">Originator</cit:CI_RoleCode> + </cit:role> + <cit:party> + <cit:CI_Organisation> + <cit:name> + <gco:CharacterString>Puertos del Estado (PdE)</gco:CharacterString> + </cit:name> + </cit:CI_Organisation> + </cit:party> + </cit:CI_Responsibility> + </cit:citedResponsibleParty> + <cit:otherCitationDetails> + <gco:CharacterString>http://marine.copernicus.eu/</gco:CharacterString> + </cit:otherCitationDetails> + </cit:CI_Citation> + </mri:citation> + <mri:pointOfContact> + <cit:CI_Responsibility> + <cit:role> + <cit:CI_RoleCode codeList="(…snip…)#CI_RoleCode" codeListValue="pointOfContact">Point of contact</cit:CI_RoleCode> + </cit:role> + <cit:party> + <cit:CI_Organisation> + <cit:name> + <gco:CharacterString>Puertos del Estado (PdE)</gco:CharacterString> + </cit:name> + </cit:CI_Organisation> + </cit:party> + </cit:CI_Responsibility> + </mri:pointOfContact> + <mri:spatialRepresentationType> + <mcc:MD_SpatialRepresentationTypeCode codeList="(…snip…)#MD_SpatialRepresentationTypeCode" codeListValue="grid">Grid</mcc:MD_SpatialRepresentationTypeCode> + </mri:spatialRepresentationType> + <mri:resourceFormat> + <mrd:MD_Format> + <mrd:formatSpecificationCitation> + <cit:CI_Citation> + <cit:title> + <gco:CharacterString>Hierarchical Data Format, version 5</gco:CharacterString> + </cit:title> + <cit:alternateTitle> + <gco:CharacterString>NetCDF-4</gco:CharacterString> + </cit:alternateTitle> + </cit:CI_Citation> + </mrd:formatSpecificationCitation> + </mrd:MD_Format> + </mri:resourceFormat> + </mri:MD_DataIdentification> + </mdb:identificationInfo> + <mdb:contentInfo> + <mrc:MD_CoverageDescription> + <mrc:attributeGroup> + <mrc:MD_AttributeGroup> + <mrc:attribute> + <mrc:MD_SampleDimension> + <mrc:sequenceIdentifier> + <gco:MemberName> + <gco:aName> + <gco:CharacterString>zos</gco:CharacterString> + </gco:aName> + <gco:attributeType> + <gco:TypeName> + <gco:aName> + <gco:CharacterString>short[865][1081][96]</gco:CharacterString> + </gco:aName> + </gco:TypeName> + </gco:attributeType> + </gco:MemberName> + </mrc:sequenceIdentifier> + <mrc:description> + <gco:CharacterString>Sea surface height</gco:CharacterString> + </mrc:description> + <mrc:name> + <mcc:MD_Identifier> + <mcc:code> + <gco:CharacterString>sea_surface_height_above_geoid</gco:CharacterString> + </mcc:code> + </mcc:MD_Identifier> + </mrc:name> + <mrc:units>m</mrc:units> + <mrc:scaleFactor> + <gco:Real>0.001</gco:Real> + </mrc:scaleFactor> + <mrc:offset> + <gco:Real>0.0</gco:Real> + </mrc:offset> + </mrc:MD_SampleDimension> + </mrc:attribute> + <mrc:attribute> + <mrc:MD_SampleDimension> + <mrc:sequenceIdentifier> + <gco:MemberName> + <gco:aName> + <gco:CharacterString>uo</gco:CharacterString> + </gco:aName> + <gco:attributeType> + <gco:TypeName> + <gco:aName> + <gco:CharacterString>short[865][1081][96]</gco:CharacterString> + </gco:aName> + </gco:TypeName> + </gco:attributeType> + </gco:MemberName> + </mrc:sequenceIdentifier> + <mrc:description> + <gco:CharacterString>Eastward velocity</gco:CharacterString> + </mrc:description> + <mrc:name> + <mcc:MD_Identifier> + <mcc:code> + <gco:CharacterString>eastward_sea_water_velocity</gco:CharacterString> + </mcc:code> + </mcc:MD_Identifier> + </mrc:name> + <mrc:units>m∕s</mrc:units> + <mrc:scaleFactor> + <gco:Real>0.001</gco:Real> + </mrc:scaleFactor> + <mrc:offset> + <gco:Real>0.0</gco:Real> + </mrc:offset> + </mrc:MD_SampleDimension> + </mrc:attribute> + <mrc:attribute> + <mrc:MD_SampleDimension> + <mrc:sequenceIdentifier> + <gco:MemberName> + <gco:aName> + <gco:CharacterString>vo</gco:CharacterString> + </gco:aName> + <gco:attributeType> + <gco:TypeName> + <gco:aName> + <gco:CharacterString>short[865][1081][96]</gco:CharacterString> + </gco:aName> + </gco:TypeName> + </gco:attributeType> + </gco:MemberName> + </mrc:sequenceIdentifier> + <mrc:description> + <gco:CharacterString>Northward velocity</gco:CharacterString> + </mrc:description> + <mrc:name> + <mcc:MD_Identifier> + <mcc:code> + <gco:CharacterString>northward_sea_water_velocity</gco:CharacterString> + </mcc:code> + </mcc:MD_Identifier> + </mrc:name> + <mrc:units>m∕s</mrc:units> + <mrc:scaleFactor> + <gco:Real>0.001</gco:Real> + </mrc:scaleFactor> + <mrc:offset> + <gco:Real>0.0</gco:Real> + </mrc:offset> + </mrc:MD_SampleDimension> + </mrc:attribute> + </mrc:MD_AttributeGroup> + </mrc:attributeGroup> + </mrc:MD_CoverageDescription> + </mdb:contentInfo> + <mdb:resourceLineage> + <mrl:LI_Lineage> + <mrl:source> + <mrl:LI_Source> + <mrl:description> + <gco:CharacterString>IBI-MFC (PdE Production Center)</gco:CharacterString> + </mrl:description> + </mrl:LI_Source> + </mrl:source> + </mrl:LI_Lineage> + </mdb:resourceLineage> + <mdb:metadataScope> + <mdb:MD_MetadataScope> + <mdb:resourceScope> + <mcc:MD_ScopeCode codeList="(…snip…)#MD_ScopeCode" codeListValue="dataset">Dataset</mcc:MD_ScopeCode> + </mdb:resourceScope> + </mdb:MD_MetadataScope> + </mdb:metadataScope> +</mdb:MD_Metadata> +{{< / highlight >}} diff --git a/content/howto/geographic_bounding_box.md b/content/howto/geographic_bounding_box.md new file mode 100644 index 00000000..403b1c36 --- /dev/null +++ b/content/howto/geographic_bounding_box.md @@ -0,0 +1,78 @@ +--- +title: Geographic bounding box of a data file +--- + +This example prints the bounding box of a GeoTIFF image. +The pixel values are not read, only the GeoTIFF file header is read. +If the file contains many images, the bounding box of each image is printed. + + +# 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 example + +The file name in following code need to be updated for yours data. + +{{< highlight java >}} +import java.io.File; +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.metadata.iso.extent.Extents; +import org.apache.sis.storage.Resource; + +public class GetBBOX { + /** + * Demo entry point. + * + * @param args ignored. + * @throws DataStoreException if an error occurred while reading the data file. + */ + public static void main(String[] args) throws DataStoreException { + try (DataStore store = DataStores.open(new File("Airport.tiff"))) { + System.out.println("For the whole file"); + System.out.println(Extents.getGeographicBoundingBox(store.getMetadata())); + if (store instanceof Aggregate agg) { + for (Resource component : agg.components()) { + System.out.println("For component " + component.getIdentifier()); + System.out.println(Extents.getGeographicBoundingBox(component.getMetadata())); + } + } + } + } +} +{{< / highlight >}} + + +# Output + +The output depends on the data and the locale. +Below is an example: + +``` +For the whole file +Geographic bounding box + ├─West bound longitude…… 2°31′33.51153867218″E + ├─East bound longitude…… 2°34′15.75923342244″E + ├─South bound latitude…… 48°59′20.7793385101″N + ├─North bound latitude…… 49°01′07.5236778991″N + └─Extent type code……………… True + +For component Optional[Airport:1] +Geographic bounding box + ├─West bound longitude…… 2°31′33.51153867218″E + ├─East bound longitude…… 2°34′15.75923342244″E + ├─South bound latitude…… 48°59′20.7793385101″N + ├─North bound latitude…… 49°01′07.5236778991″N + └─Extent type code……………… True +``` diff --git a/content/howto/raster_values_at_geographic_coordinates.md b/content/howto/raster_values_at_geographic_coordinates.md index 5dd8ecf1..1618e45b 100644 --- a/content/howto/raster_values_at_geographic_coordinates.md +++ b/content/howto/raster_values_at_geographic_coordinates.md @@ -122,9 +122,9 @@ public class RasterValuesAtGeographicCoordinates { * @throws DataStoreException if an error occurred while reading the raster. */ private static void printComponents(DataStore store) throws DataStoreException { - if (store instanceof Aggregate a) { + if (store instanceof Aggregate agg) { System.out.println("Components found in the data store:"); - for (Resource component : a.components()) { + for (Resource component : agg.components()) { component.getIdentifier().ifPresent((id) -> System.out.println("- " + id)); } } else { diff --git a/content/howto/transform_envelopes.md b/content/howto/transform_envelopes.md new file mode 100644 index 00000000..9d8cb450 --- /dev/null +++ b/content/howto/transform_envelopes.md @@ -0,0 +1,105 @@ +--- +title: Transform envelopes (bounding boxes) +--- + +The transformation of envelopes (or bounding boxes) is a much more difficult task +than transforming the four corners of a rectangle. +The rectangle straight lines in source {{% CRS %}} may become curves in the target {{% CRS %}} +with minimum and maximum values that are not located in any corner. +The calculation is also more complicated if the envelope contains a pole or crosses the anti-meridian. +Apache {{% SIS %}} handles those complexities in convenience static methods. +This is demonstrated by comparing the result of using Apache {{% SIS %}} methods +with the result of transforming the four corners. +The latter naive approach has an error of 40 kilometres in this example. + +While the example below uses a two-dimensional envelope, +the same Apache {{% SIS %}} methods can transform efficiently _N_-dimensional envelopes. + + +# Direct dependencies + +Maven coordinates | Module info +------------------------------------------- | ---------------------------- +`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.opengis.geometry.Envelope; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.CoordinateOperation; +import org.opengis.referencing.operation.TransformException; +import org.opengis.util.FactoryException; +import org.apache.sis.referencing.CRS; +import org.apache.sis.referencing.CommonCRS; +import org.apache.sis.geometry.Envelopes; +import org.apache.sis.geometry.GeneralEnvelope; +import org.apache.sis.geometry.DirectPosition2D; + +public class TransformEnvelopes { + /** + * Demo entry point. + * + * @param args ignored. + * @throws FactoryException if an error occurred while creating a Coordinate Reference System (CRS). + * @throws TransformException if an error occurred while transforming coordinates to the target CRS. + */ + public static void main(String[] args) throws FactoryException, TransformException { + CoordinateReferenceSystem sourceCRS = CommonCRS.WGS84.geographic(); + CoordinateReferenceSystem targetCRS = CommonCRS.WGS84.universal(90, 0); // Polar stereographic. + + GeneralEnvelope bbox = new GeneralEnvelope(sourceCRS); + bbox.setRange(0, 84, 88); // Latitudes. + bbox.setRange(1, -20, 50); // Longitudes. + + Envelope transformed = Envelopes.transform(bbox, targetCRS); + Envelope corners = transformCorners(bbox, CRS.findOperation(sourceCRS, targetCRS, null)); + + System.out.println("Source: " + bbox); + System.out.println("Result: " + transformed); + System.out.println("Corners: " + corners); + System.out.println("Errors on Y axis: " + + (corners.getMinimum(1) - transformed.getMinimum(1)) + " metres."); + } + + /** + * Transforms the 4 corners of a two-dimensional envelope. This is not recommended. + * This method is provided only for demonstrating that this approach is not sufficient. + * + * @param bbox the bounding box to transform. + * @param operation the coordinate operation to use for transforming corners. + * @return the result of transforming the 4 corners of the provided bounding box. + * @throws TransformException if a coordinate can not be converted. + */ + private static Envelope transformCorners(Envelope bbox, CoordinateOperation operation) throws TransformException { + double[] corners = { + bbox.getMinimum(0), bbox.getMinimum(1), + bbox.getMaximum(0), bbox.getMinimum(1), + bbox.getMaximum(0), bbox.getMaximum(1), + bbox.getMinimum(0), bbox.getMaximum(1) + }; + operation.getMathTransform().transform(corners, 0, corners, 0, 4); + GeneralEnvelope result = new GeneralEnvelope(operation.getTargetCRS()); + for (int i=0; i<result.getDimension(); i++) { + result.setRange(i, corners[i], corners[i]); + } + for (int i=0; i<corners.length;) { + result.add(new DirectPosition2D(corners[i++], corners[i++])); + } + return result; + } +} +{{< / highlight >}} + + +# Output + +``` +Source: BOX(84 -20, 88 50) +Result: BOX(1771965.695226812 1333272.44494046, 2510743.0524805877 1857256.625440407) +Corners: BOX(1771965.695226812 1373480.89677463, 2510743.0524805877 1857256.625440407) +Errors on Y axis: 40208.451834172476 metres. +```