All Downloads are FREE. Search and download functionalities are using the official Maven repository.

utility.GdalUtility Maven / Gradle / Ivy

The newest version!
/* The MIT License (MIT)
 *
 * Copyright (c) 2015 Reinventing Geospatial, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package utility;

import com.rgi.common.BoundingBox;
import com.rgi.common.Dimensions;
import com.rgi.common.Range;
import com.rgi.common.coordinate.Coordinate;
import com.rgi.common.coordinate.CoordinateReferenceSystem;
import com.rgi.common.coordinate.CrsCoordinate;
import com.rgi.common.coordinate.referencesystem.profile.CrsProfile;
import com.rgi.common.coordinate.referencesystem.profile.CrsProfileFactory;
import com.rgi.common.tile.TileOrigin;
import com.rgi.common.tile.scheme.TileMatrixDimensions;
import com.rgi.common.tile.scheme.TileScheme;
import com.rgi.common.tile.scheme.ZoomTimesTwo;
import com.rgi.g2t.GeoTransformation;
import com.rgi.g2t.TilingException;
import com.rgi.store.tiles.TileStoreException;
import org.gdal.gdal.Band;
import org.gdal.gdal.Dataset;
import org.gdal.gdal.gdal;
import org.gdal.gdalconst.gdalconstConstants;
import org.gdal.osr.SpatialReference;
import org.gdal.osr.osr;

import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BandedSampleModel;
import java.awt.image.BufferedImage;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferShort;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.zip.DataFormatException;

/**
 * Common functionality of the GDAL library made into helper functions
 *
 * @author Luke D. Lambert
 * @author Steven D. Lander
 */
public final class GdalUtility
{
    static
    {
        // GDAL_DATA needs to be a valid path
        if(System.getenv("GDAL_DATA") == null)
        {
            throw new RuntimeException("Tiling will not work without GDAL_DATA environment variable.");
        }
        // Get the system path
        //String paths = System.getenv("PATH");
        // TODO
        // Parse the path entries
        // Check each path entry for the required dll's/so's
        // Throw an error if any of the required ones are missing

        osr.UseExceptions();
        gdal.AllRegister(); // Register GDAL extensions
    }

    private GdalUtility()
    {
        // Empty constructor for private class
    }

    /**
     * Opens an image file, and returns a {@link Dataset}
     *
     * @param rawImage A raster image {@link File}
     * @return A {@link Dataset} warped to the input coordinate reference system
     */
    public static Dataset open(final File rawImage)
    {
        return GdalUtility.open(rawImage, null);
    }

    /**
     * Opens an image file, and returns a {@link Dataset}
     *
     * @param rawImage                  A raster image {@link File}
     * @param coordinateReferenceSystem The {@link CoordinateReferenceSystem} the tiles should be
     *                                  output in
     * @return A {@link Dataset} warped to the input coordinate reference system
     */
    public static Dataset open(final File rawImage, final CoordinateReferenceSystem coordinateReferenceSystem)
    {
        final Dataset dataset = gdal.Open(rawImage.getAbsolutePath()); // Opening is read-only by default

        if(dataset == null)
        {
            throw new RuntimeException(new GdalError().getMessage());
        }

        if(coordinateReferenceSystem == null)
        {
            return dataset;
        }

        try
        {
            final Dataset openDataset = GdalUtility.doesDataSetMatchCRS(dataset, coordinateReferenceSystem)
                                        ? GdalUtility.warpDatasetToSrs(dataset,
                                                                       GdalUtility.getSpatialReference(dataset),
                                                                       GdalUtility.getSpatialReference(coordinateReferenceSystem))
                                        : GdalUtility.reprojectDatasetToSrs(dataset,
                                                                            GdalUtility.getSpatialReference(dataset),
                                                                            GdalUtility.getSpatialReference(coordinateReferenceSystem));

            if(openDataset == null)
            {
                throw new RuntimeException(new GdalError().getMessage());
            }

            return openDataset;
        }
        catch(IOException | TilingException exception)
        {
            throw new RuntimeException(exception);
        }
        finally
        {
            dataset.delete();
        }
    }

    public static boolean doesDataSetMatchCRS(final Dataset d1, final CoordinateReferenceSystem crs)
    {
        if(!GdalUtility.getSpatialReference(d1).equals(GdalUtility.getSpatialReference(crs)))
        {
            final SpatialReference fromSrs = GdalUtility.getSpatialReference(d1);
            final SpatialReference toSrs   = GdalUtility.getSpatialReference(crs);

            final String fromAuthority0 = fromSrs.GetAttrValue("AUTHORITY", 0);
            final String toAuthority0 = toSrs.GetAttrValue("AUTHORITY", 0);
            final String fromAuthority1 = fromSrs.GetAttrValue("AUTHORITY", 1);
            final String toAuthority1 = toSrs.GetAttrValue("AUTHORITY", 1);

            if(fromAuthority0 == null || fromAuthority1 == null)
            {
                return false;
            }

            return fromAuthority0.equals(toAuthority0) && fromAuthority1.equals(toAuthority1);
        }
        return true;
    }

    /**
     * Converts a GDAL {@link Dataset} into a {@link BufferedImage} 
*
* Code based on GDAL/SWIG example found here. * * @param dataset A GDAL {@link Dataset} * @return Returns a {@link BufferedImage} whose contents matches that of * the input GDAL {@link Dataset} */ public static BufferedImage convert(final Dataset dataset) { if(dataset == null) { throw new IllegalArgumentException("Dataset may not be null"); } final int bandCount = dataset.getRasterCount(); if(bandCount <= 0) { throw new RuntimeException("Raster contained no bands"); } final int rasterWidth = dataset.getRasterXSize(); final int rasterHeight = dataset.getRasterYSize(); final int pixelCount = rasterWidth * rasterHeight; final Band band = dataset.GetRasterBand(1); // Bands are 1-base indexed if(band == null) { throw new RuntimeException("GDAL returned a null raster band"); } final int bandDataType = band.getDataType(); if(bandCount == 1) { if(band.GetRasterColorInterpretation() == gdalconstConstants.GCI_PaletteIndex && band.GetRasterColorTable() != null) { final ByteBuffer byteBuffer = band.ReadRaster_Direct(0, 0, band.getXSize(), band.getYSize(), band.getDataType()); final DataBuffer dataBuffer = getDataBuffer(band.getDataType(), bandCount, pixelCount, new ByteBuffer[]{byteBuffer}); final int dataBufferType = getDataBufferType(bandDataType); final SampleModel sampleModel = new BandedSampleModel(dataBufferType, rasterWidth, rasterHeight, bandCount); final WritableRaster raster = Raster.createWritableRaster(sampleModel, dataBuffer, null); //bufferedImageDataType = BufferedImage.TYPE_BYTE_INDEXED; // This assignment had no effect return new BufferedImage(band.GetRasterColorTable() .getIndexColorModel(gdal.GetDataTypeSize(bandDataType)), raster, false, null); } } final ByteBuffer[] bands = IntStream.range(0, bandCount) .mapToObj(bandIndex -> { final Band currentBand = dataset.GetRasterBand(bandIndex + 1); return currentBand.ReadRaster_Direct(0, 0, currentBand.getXSize(), currentBand.getYSize(), currentBand.getDataType()); }) .toArray(ByteBuffer[]::new); final DataBuffer dataBuffer = getDataBuffer(bandDataType, bandCount, pixelCount, bands); final int dataBufferType = getDataBufferType(bandDataType); final SampleModel sampleModel = new BandedSampleModel(dataBufferType, rasterWidth, rasterHeight, bandCount); final WritableRaster raster = Raster.createWritableRaster(sampleModel, dataBuffer, null); if(bandCount > 2) { return new BufferedImage(new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), bandCount == 4, false, bandCount == 4 ? Transparency.TRANSLUCENT : Transparency.OPAQUE, dataBufferType), raster, true, null); } final BufferedImage bufferedImage = new BufferedImage(rasterWidth, rasterHeight, getBufferedImageDataType(bandDataType)); bufferedImage.setData(raster); return bufferedImage; } private static DataBuffer getDataBuffer(final int bandDataType, final int bandCount, final int pixelCount, final ByteBuffer[] bands) { if(bandDataType == gdalconstConstants.GDT_Byte) { final byte[][] bytes = new byte[bandCount][]; for(int i = 0; i < bandCount; i++) { bytes[i] = new byte[pixelCount]; bands[i].get(bytes[i]); } return new DataBufferByte(bytes, pixelCount); } else if(bandDataType == gdalconstConstants.GDT_Int16) { final short[][] shorts = new short[bandCount][]; for(int i = 0; i < bandCount; i++) { shorts[i] = new short[pixelCount]; bands[i].asShortBuffer().get(shorts[i]); } return new DataBufferShort(shorts, pixelCount); } else if(bandDataType == gdalconstConstants.GDT_Int32) { final int[][] ints = new int[bandCount][]; for(int i = 0; i < bandCount; i++) { ints[i] = new int[pixelCount]; bands[i].asIntBuffer().get(ints[i]); } return new DataBufferInt(ints, pixelCount); } else { throw new IllegalArgumentException("Unsupported band data type"); } } private static int getDataBufferType(final int bandDataType) { if(bandDataType == gdalconstConstants.GDT_Byte) { return DataBuffer.TYPE_BYTE; } else if(bandDataType == gdalconstConstants.GDT_Int16) { return DataBuffer.TYPE_USHORT; } else if(bandDataType == gdalconstConstants.GDT_Int32) { return DataBuffer.TYPE_INT; } else { throw new IllegalArgumentException("Unsupported band data type"); } } private static int getBufferedImageDataType(final int bandDataType) { if(bandDataType == gdalconstConstants.GDT_Byte) { return BufferedImage.TYPE_BYTE_GRAY; } else if(bandDataType == gdalconstConstants.GDT_Int16) { return BufferedImage.TYPE_USHORT_GRAY; } else if(bandDataType == gdalconstConstants.GDT_Int32) { return BufferedImage.TYPE_CUSTOM; } else { throw new IllegalArgumentException("Unsupported band data type"); } } /** * Gets the GDAL {@link SpatialReference} from the input {@link Dataset} * * @param dataset A GDAL {@link Dataset} * @return Returns the GDAL {@link SpatialReference} of the dataset */ public static SpatialReference getSpatialReference(final Dataset dataset) { if(dataset == null) { throw new IllegalArgumentException("Dataset may not be null."); } String wkt = dataset.GetProjection(); // Get the well-known-text of this dataset if(wkt.isEmpty() && dataset.GetGCPCount() != 0) // If the WKT is empty and there are GCPs... { wkt = dataset.GetGCPProjection(); } final SpatialReference srs = new SpatialReference(); srs.ImportFromWkt( wkt); // Returns 0 on success. Otherwise throws a RuntimeException() (or an error code if DontUseExceptions() has been called). return srs; } /** * Get the {@link SpatialReference} from an image file * * @param file Image file * @return The {@link SpatialReference} of the image file */ public static SpatialReference getSpatialReference(final File file) { if(file == null || !file.canRead()) { throw new IllegalArgumentException("File may not be null, and must be readable"); } final Dataset dataset = GdalUtility.open(file); try { return GdalUtility.getSpatialReference(dataset); } finally { dataset.delete(); } } /** * Given a {@link CoordinateReferenceSystem}, return a {@link SpatialReference} * * @param crs An input {@link CoordinateReferenceSystem} * @return A {@link SpatialReference} built from the input CRS WKT */ public static SpatialReference getSpatialReference(final CoordinateReferenceSystem crs) { if(crs == null) { throw new IllegalArgumentException("Coordinate reference system cannot be null."); } final SpatialReference srs = new SpatialReference(); srs.ImportFromWkt(CrsProfileFactory.create(crs).getWellKnownText()); return srs; } public static SpatialReference createSpatialReference(final CoordinateReferenceSystem coordinateReferenceSystem) { final SpatialReference spatialReferenceSystem = new SpatialReference(); final int identifier = coordinateReferenceSystem.getIdentifier(); final String authority = coordinateReferenceSystem.getAuthority() .toUpperCase(); final int srsImportError; switch(authority) { case "EPSG": srsImportError = spatialReferenceSystem.ImportFromEPSG(identifier); break; case "EPSGA": srsImportError = spatialReferenceSystem.ImportFromEPSGA(identifier); break; default: throw new RuntimeException("Currently only EPSG and EPSGA codes can be be used in a coordinate reference systems"); } if(srsImportError != gdalconstConstants.CE_None) { throw new RuntimeException(new GdalError().getMessage()); } return spatialReferenceSystem; } /** * Provide a {@link SpatialReference} given an input {@link CrsProfile} * * @param crsProfile A {@link CrsProfile} from which a {@link SpatialReference} should be built * @return A {@link SpatialReference} built from the input CrsProfile */ public static SpatialReference getSpatialReference(final CrsProfile crsProfile) { if(crsProfile == null) { throw new IllegalArgumentException("Crs Profile cannot be null."); } return GdalUtility.getSpatialReference(crsProfile.getCoordinateReferenceSystem()); } /** * Determine if an input dataset has a georeference * * @param dataset An input {@link Dataset} * @return A boolean where true means the dataset has a georeference and false otherwise */ public static boolean hasGeoReference(final Dataset dataset) { if(dataset == null) { throw new IllegalArgumentException("Input dataset cannot be null."); } final double[] identityTransform = {0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; // Compare the dataset's transform to the identity transform and ensure there are no GCPs return !Arrays.equals(dataset.GetGeoTransform(), identityTransform) || dataset.GetGCPCount() != 0; } /** * Get the bounding box for an input {@link Dataset} * * @param dataset An input {@link Dataset} * @return A {@link BoundingBox} built from the bounds of the input {@link Dataset} * @throws DataFormatException When the input dataset contains rotation or skew. Fix the * input raster with the {@code gdalwarp} utility * manually. */ public static BoundingBox getBounds(final Dataset dataset) throws DataFormatException { if(dataset == null) { throw new IllegalArgumentException("Input dataset cannot be null."); } return new GeoTransformation(dataset.GetGeoTransform()).getBounds(new Dimensions<>(dataset.getRasterXSize(), dataset.getRasterYSize())); } /** * Build a {@link CoordinateReferenceSystem} from an input {@link SpatialReference} * * @param srs An input {@link SpatialReference} from which a {@link CoordinateReferenceSystem} will be built * @return A {@link CoordinateReferenceSystem} built from the input {@link SpatialReference} using the * authority name and code. */ public static CoordinateReferenceSystem getCoordinateReferenceSystem(final SpatialReference srs) { if(srs == null) { throw new IllegalArgumentException("Input spatial reference system cannot be null."); } // Passing null to GetAuthorityName and Code will query the root node of the WKT, not // sure if this is what we want final String authority = srs.GetAuthorityName(null); final String identifier = srs.GetAuthorityCode(null); // final String attributePath = "PROJCS|GEOGCS|AUTHORITY"; // https://gis.stackexchange.com/questions/20298/ // // final String authority = srs.GetAttrValue(attributePath, 0); // final String identifier = srs.GetAttrValue(attributePath, 1); if(authority == null || authority.isEmpty() || identifier == null || identifier.isEmpty()) { return null; } return new CoordinateReferenceSystem(getName(srs), authority, Integer.valueOf(identifier)); } /** * Gets the name of a {@link SpatialReference} * * @param spatialReference Spatial reference * @return The name of a {@link SpatialReference}. This is the first * attribute after by a top level entry in the WKT. By the * WKT specification, the only valid top level entries are: * "PROJCS", "GEOGCS", "GEOCCS" and each of their first attributes * must be the name of the spatial reference system. */ public static String getName(final SpatialReference spatialReference) { if(spatialReference == null) { throw new IllegalArgumentException("Input spatial reference cannot be null."); } return Arrays.asList("PROJCS", "GEOGCS", "GEOCCS") // These are all of the top level strings according to http://portal.opengeospatial.org/files/?artifact_id=25355. They must all be followed by a name attribute. .stream() .map(srsType -> spatialReference.GetAttrValue(srsType, 0)) .filter(Objects::nonNull) .findFirst() .orElse(null); } /** * Build a {@link CrsProfile} from an input {@link Dataset} * * @param dataset An input {@link Dataset} * @return A {@link CrsProfile} built from the input {@link Dataset} */ public static CrsProfile getCrsProfile(final Dataset dataset) { if(dataset == null) { throw new IllegalArgumentException("Input dataset cannot be null."); } final SpatialReference srs = GdalUtility.getSpatialReference(dataset); final CoordinateReferenceSystem crs = GdalUtility.getCoordinateReferenceSystem(srs); return CrsProfileFactory.create(crs); } /** * Calculate all the tile ranges for the data in the input {@link * BoundingBox} for the given zoom levels. * * @param tileScheme A {@link TileScheme} describing tile matrices for a set of * zoom levels * @param datasetBounds A {@link BoundingBox} describing the data area * @param tileMatrixBounds A {@link BoundingBox} describing the area of the tile * matrix. * @param crsProfile A {@link CrsProfile} for the input area * @param tileOrigin A {@link TileOrigin} that represents which corner tiling * begins from * @return A {@link Map} of zoom levels to tile coordinate info for the * top left and bottom right corners of the matrix */ public static Map>> calculateTileRanges(final TileScheme tileScheme, final BoundingBox datasetBounds, final BoundingBox tileMatrixBounds, final CrsProfile crsProfile, final TileOrigin tileOrigin) { if(datasetBounds == null) { throw new IllegalArgumentException("Input bounds cannot be null."); } if(crsProfile == null) { throw new IllegalArgumentException("Input crs profile cannot be null."); } if(tileScheme == null) { throw new IllegalArgumentException("Input tile scheme cannot be null."); } if(tileOrigin == null) { throw new IllegalArgumentException("Input tile origin cannot be null."); } // Get the CRS coordinates of the bounds final CrsCoordinate topLeft = new CrsCoordinate(datasetBounds.getTopLeft(), crsProfile.getCoordinateReferenceSystem()); final CrsCoordinate bottomRight = new CrsCoordinate(datasetBounds.getBottomRight(), crsProfile.getCoordinateReferenceSystem()); return tileScheme.getZoomLevels() .stream() .collect(Collectors.toMap(zoom -> zoom, zoom -> { final TileMatrixDimensions tileMatrixDimensions = tileScheme.dimensions(zoom); final Coordinate topLeftTile = crsProfile.crsToTileCoordinate(topLeft, tileMatrixBounds, tileMatrixDimensions, tileOrigin); final Coordinate bottomRightTile = crsProfile.crsToTileCoordinate(bottomRight, tileMatrixBounds, tileMatrixDimensions, tileOrigin); return new Range<>(topLeftTile, bottomRightTile); })); } /** * Returns the highest numeric Zoom level in which the entire dataset can be viewed as a single tile. * * @param tileRanges The calculated list of tile numbers and zooms * @return The zoom level for this dataset that produces only one tile. * Defaults to 0 if an error occurs. */ public static int getMinimalZoom(final Map>> tileRanges) { if(tileRanges == null || tileRanges.isEmpty()) { throw new IllegalArgumentException("Tile Range Map cannot be null or Empty"); } final Range> levelZero = tileRanges.get(0); if(!levelZero.getMinimum().equals(levelZero.getMaximum())) { throw new IllegalArgumentException("Level Zero of the tileRange must be 1 tile"); } return tileRanges.entrySet() .stream() .filter(Objects::nonNull) .filter(entry -> { //if its 1 tile return entry.getValue().getMinimum().equals(entry.getValue().getMaximum()); }) .reduce((previous, current) -> { //get the last entry. return current; }).get().getKey(); } /** * Get the highest-integer zoom level for the input {@link Dataset} * * @param dataset An input {@link Dataset} * @param tileRanges The calculated list of tile numbers and zooms * @param tileOrigin The {@link TileOrigin} of the tile grid * @param tileScheme The {@link TileScheme} of the tile grid * @param tileSize A {@link Dimensions} Integer object that describes what the tiles should look like * @return The zoom level for this dataset that is closest to the actual * resolution * @throws TileStoreException Thrown when a {@link GdalUtility#getCrsProfile(Dataset)} throws */ public static int getMaximalZoom(final Dataset dataset, final Map>> tileRanges, final TileOrigin tileOrigin, final TileScheme tileScheme, final Dimensions tileSize) throws TileStoreException { if(dataset == null) { throw new IllegalArgumentException("Input dataset cannot be null."); } if(tileRanges == null || tileRanges.isEmpty()) { throw new IllegalArgumentException("Tile range list cannot be null or empty."); } if(tileOrigin == null) { throw new IllegalArgumentException("Tile origin cannot be null."); } if(tileScheme == null) { throw new IllegalArgumentException("Tile scheme cannot be null."); } if(tileSize == null) { throw new IllegalArgumentException("Tile dimensions cannot be null."); } final double zoomPixelSize = dataset.GetGeoTransform()[1]; try { final CrsProfile crsProfile = GdalUtility.getCrsProfile(dataset); return GdalUtility.zoomLevelForPixelSize(zoomPixelSize, tileRanges, dataset, crsProfile, tileScheme, tileOrigin, tileSize); } catch(final TileStoreException e) { throw new TileStoreException("Could not determine maximum zoom level."); } } /** * Return a {@link Set} of all the zoom levels in the input {@link Dataset} * * @param dataset An input {@link Dataset} * @param tileOrigin The {@link TileOrigin} of the tile grid * @param tileSize A {@link Dimensions} Integer object that describes what the * tiles should look like * @return A set of integers for all the zoom levels in the input dataset * @throws TileStoreException Thrown if the input dataset bounds could not * be retrieved */ public static Set getZoomLevels(final Dataset dataset, final TileOrigin tileOrigin, final Dimensions tileSize) throws TileStoreException { if(dataset == null) { throw new IllegalArgumentException("Input dataset cannot be null."); } if(tileOrigin == null) { throw new IllegalArgumentException("Tile origin cannot be null."); } if(tileSize == null) { throw new IllegalArgumentException("Tile dimensions cannot be null."); } // World extent tile scheme final ZoomTimesTwo tileScheme = new ZoomTimesTwo(0, 31, 1, 1); try { final BoundingBox datasetBounds = GdalUtility.getBounds(dataset); final CrsProfile crsProfile = GdalUtility.getCrsProfile(dataset); final Map>> tileRanges = GdalUtility.calculateTileRanges(tileScheme, datasetBounds, crsProfile.getBounds(), crsProfile, tileOrigin); final int minZoom = GdalUtility.getMinimalZoom(tileRanges); final int maxZoom = GdalUtility.getMaximalZoom(dataset, tileRanges, tileOrigin, tileScheme, tileSize); return IntStream.rangeClosed(minZoom, maxZoom) .boxed() .collect(Collectors.toSet()); } catch(final DataFormatException dfe) { throw new TileStoreException(dfe); } } /** * Return the appropriate zoom level based on the input pixel resolution * * @param zoomPixelSize The pixel resolution of the zoom level * @param tileRanges The calculated list of tile numbers and zooms * @param dataset An input {@link Dataset} * @param crsProfile A {@link CrsProfile} for the input area * @param tileScheme The {@link TileScheme} of the tile grid * @param tileOrigin The {@link TileOrigin} of the tile grid * @param tileSize A {@link Dimensions} Integer object that describes what the * tiles should look like * @return The integer zoom matched to the pixel resolution * @throws TileStoreException When the bounds of the dataset could not be determined */ public static int zoomLevelForPixelSize(final double zoomPixelSize, final Map>> tileRanges, final Dataset dataset, final CrsProfile crsProfile, final TileScheme tileScheme, final TileOrigin tileOrigin, final Dimensions tileSize) throws TileStoreException { if(tileRanges == null || tileRanges.isEmpty()) { throw new IllegalArgumentException("Tile range list cannot be null."); } if(dataset == null) { throw new IllegalArgumentException("Input dataset cannot be null."); } if(crsProfile == null) { throw new IllegalArgumentException("Crs profile cannot be null."); } if(tileScheme == null) { throw new IllegalArgumentException("Tile scheme cannot be null."); } if(tileOrigin == null) { throw new IllegalArgumentException("Tile origin cannot be null."); } if(tileSize == null) { throw new IllegalArgumentException("Tile dimensions cannot be null."); } try { final BoundingBox boundingBox = GdalUtility.getBounds(dataset); final int zoomLevelForPixelSize = tileRanges.entrySet() .stream() .filter(entrySet -> { return zoomLevelForPixelSize( tileScheme.dimensions(entrySet.getKey()), zoomPixelSize, entrySet.getValue(), crsProfile, tileScheme, tileOrigin, tileSize, boundingBox); }) .map(entrySet -> entrySet.getKey()) .findFirst() .orElseThrow( () -> new NumberFormatException("Could not determine zoom level for pizel size: " + String.valueOf( zoomPixelSize))); return zoomLevelForPixelSize == 0 ? 0 : zoomLevelForPixelSize - 1; } catch(DataFormatException | NumberFormatException ex) { throw new TileStoreException(ex); } } private static boolean zoomLevelForPixelSize(final TileMatrixDimensions tileMatrixDimensions, final double zoomPixelSize, final Range> tileRange, final CrsProfile crsProfile, final TileScheme tileScheme, final TileOrigin tileOrigin, final Dimensions tileSize, final BoundingBox boundingBox) { if(tileMatrixDimensions == null) { throw new IllegalArgumentException(); } if(tileRange == null) { throw new IllegalArgumentException("Tile range cannot be null."); } if(crsProfile == null) { throw new IllegalArgumentException("Crs profile cannot be null."); } if(tileScheme == null) { throw new IllegalArgumentException("Tile scheme cannot be null."); } if(tileOrigin == null) { throw new IllegalArgumentException("Tile origin cannot be null."); } if(tileSize == null) { throw new IllegalArgumentException("Tile dimensions cannot be null."); } // Get the tile coordinates of the top-left and bottom-right tiles final Coordinate topLeftTile = crsProfile.crsToTileCoordinate(new CrsCoordinate(boundingBox.getTopLeft(), crsProfile.getCoordinateReferenceSystem()), crsProfile.getBounds(), // Use bounds of the world here tileMatrixDimensions, tileOrigin); final Coordinate bottomRightTile = crsProfile.crsToTileCoordinate(new CrsCoordinate(boundingBox.getBottomRight(), crsProfile.getCoordinateReferenceSystem()), crsProfile.getBounds(), //boundingBox, Use bounds of the world here tileMatrixDimensions, tileOrigin); // Convert tile coordinates to crs coordinates: this will give us correct units-of-measure-per-pixel final Coordinate topLeftCoord = tileOrigin.transform(TileOrigin.UpperLeft, topLeftTile.getX(), topLeftTile.getY(), tileMatrixDimensions); final Coordinate bottomRightCoord = tileOrigin.transform(TileOrigin.LowerRight, bottomRightTile.getX(), bottomRightTile.getY(), tileMatrixDimensions); final CrsCoordinate topLeftCrsFull = crsProfile.tileToCrsCoordinate(topLeftCoord.getX(), topLeftCoord.getY(), crsProfile.getBounds(), tileMatrixDimensions, TileOrigin.UpperLeft); final CrsCoordinate bottomRightCrsFull = crsProfile.tileToCrsCoordinate(bottomRightCoord.getX(), bottomRightCoord.getY(), crsProfile.getBounds(), tileMatrixDimensions, TileOrigin.LowerRight); // get how many tiles wide this zoom will be so that number can be multiplied by tile size // TODO *WARNING* 'tiles wide' is used for both width and height calculations! final int zoomTilesWide = tileRange.getMaximum().getX() - tileRange.getMinimum().getX() + 1; final double zoomResolution; if(tileSize.getWidth() >= tileSize.getHeight()) { final double width = bottomRightCrsFull.getX() - topLeftCrsFull.getX(); zoomResolution = width / (zoomTilesWide * tileSize.getWidth()); } else { final double height = topLeftCrsFull.getY() - bottomRightCrsFull.getY(); zoomResolution = height / (zoomTilesWide * tileSize.getHeight()); } return zoomPixelSize > zoomResolution; } /** * Warp an input {@link Dataset} into a different spatial reference system. Does * not correct for NODATA values. * * @param dataset An input {@link Dataset} * @param fromSrs Original spatial reference system of the {@code dataset} * @param toSrs Spatial reference system to warp the {@code dataset} to * @return A {@link Dataset} in the input {@link SpatialReference} requested */ public static Dataset warpDatasetToSrs(final Dataset dataset, final SpatialReference fromSrs, final SpatialReference toSrs) { if(dataset == null) { throw new IllegalArgumentException("Input dataset cannot be null."); } if(fromSrs == null) { throw new IllegalArgumentException("From-Srs cannot be null."); } if(toSrs == null) { throw new IllegalArgumentException("To-Srs cannot be null."); } final Dataset output = gdal.AutoCreateWarpedVRT(dataset, fromSrs.ExportToWkt(), toSrs.ExportToWkt(), gdalconstConstants.GRA_Average); if(output == null) { throw new RuntimeException(new GdalError().getMessage()); } return output; } /** * Reproject an input {@link Dataset} into a different spatial reference system. Does * not correct for NODATA values. * * @param dataset An input {@link Dataset} * @param fromSrs Original spatial reference system of the {@code dataset} * @param toSrs Spatial reference system to warp the {@code dataset} to * @return A {@link Dataset} in the input {@link SpatialReference} requested * @throws IOException * @throws TilingException */ public static Dataset reprojectDatasetToSrs(final Dataset dataset, final SpatialReference fromSrs, final SpatialReference toSrs) throws IOException, TilingException { if(dataset == null) { throw new IllegalArgumentException("Input dataset cannot be null."); } if(fromSrs == null) { throw new IllegalArgumentException("From-Srs cannot be null."); } if(toSrs == null) { throw new IllegalArgumentException("To-Srs cannot be null."); } final Path path = File.createTempFile("Reprojection", ".tiff").toPath(); final Dataset output = gdal.GetDriverByName("GTiff").Create(path.toString(), dataset.getRasterXSize(), dataset.getRasterYSize(), dataset.getRasterCount()); output.SetProjection(toSrs.ExportToWkt()); final Dataset temp = gdal.AutoCreateWarpedVRT(dataset, fromSrs.ExportToWkt(), toSrs.ExportToWkt(), gdalconstConstants.GRA_Average); if(temp == null) { throw new RuntimeException(new GdalError().getMessage()); } output.SetGeoTransform(temp.GetGeoTransform()); temp.delete(); final int result = gdal.ReprojectImage(dataset, output, fromSrs.ExportToWkt(), toSrs.ExportToWkt()); if(result != gdalconstConstants.CE_None) { //remove database on error output.delete(); Files.delete(path); if(result == gdalconstConstants.CE_Failure) { throw new IOException("Tile call outside of raster bounds."); } if(result == gdalconstConstants.CE_Fatal) { throw new TilingException("Fatal error detected from GDAL readRaster."); } } return output; } /** * Scale a Dataset down into a smaller-sized Dataset using the average algorithm. * * @param queryDataset A {@link Dataset} that needs to be scaled down to a smaller size * @param dimensions A {@link Dimensions} object containing the width and height * information for the output {@link Dataset} * @return A {@link Dataset} of the sizespecified in the width and height properties of * the input {@link Dimensions} object * @throws TilingException Thrown when any band of the input query {@link Dataset} fails * to scale correctly with {@link gdal#RegenerateOverview(Band, Band, String)} */ public static Dataset scaleQueryToTileSize(final Dataset queryDataset, final Dimensions dimensions) throws TilingException { if(queryDataset == null) { throw new IllegalArgumentException("Query dataset cannot be null."); } if(dimensions == null) { throw new IllegalArgumentException("Tile dimensions cannot be null."); } // TODO: This just handles average resampling, it should be adjusted for other resampling types final Dataset tileDataInMemory = gdal.GetDriverByName("MEM").Create("", dimensions.getWidth(), dimensions.getHeight(), queryDataset.GetRasterCount()); try { IntStream.rangeClosed(1, queryDataset.GetRasterCount()) .forEach(index -> { final int resolution = gdal.RegenerateOverview(queryDataset.GetRasterBand(index), tileDataInMemory.GetRasterBand(index), "average"); if(resolution != 0) { throw new RuntimeException("Could not regenerate overview on band: " + String.valueOf(index)); } }); } catch(final RuntimeException ex) { throw new TilingException(ex); } return tileDataInMemory; } /** * Get the color values specified as NODATA in a Dataset. * * @param dataset An input {@link Dataset} that possibly has NODATA values * @return The NODATA values as a {@link Double} array */ public static Double[] getNoDataValues(final Dataset dataset) { if(dataset == null) { throw new IllegalArgumentException("Input dataset cannot be null."); } // Initialize a new double array of size 3 final Double[] noDataValues = new Double[4]; // Get the nodata value for each band IntStream.rangeClosed(1, dataset.GetRasterCount()) .forEach(band -> { final Double[] noDataValue = new Double[1]; dataset.GetRasterBand(band).GetNoDataValue(noDataValue); if(noDataValue.length != 0 && noDataValue[0] != null) { // Assumes only one value coming back from the band noDataValues[band - 1] = noDataValue[0]; } }); // Is array still using the initialized values? if(noDataValues[0] == null && noDataValues[1] == null && noDataValues[2] == null) { return new Double[0]; } // TODO: Is it possible to see a raster from GDAL with 2 bands? I think // only Mono and RGB options are possible if(noDataValues[0] != null) { noDataValues[1] = noDataValues[0]; noDataValues[2] = noDataValues[0]; noDataValues[3] = noDataValues[0]; } return noDataValues; } /** * Get the number of raster {@link Band}s in a Dataset. * * @param dataset An input {@link Dataset} containing a number of bands * @param alphaBand An alpha {@link Band} * @return The number of {@link Band}s in the input {@link Dataset} */ public static int getRasterBandCount(final Dataset dataset, final Band alphaBand) { if(dataset == null) { throw new IllegalArgumentException("Input dataset cannot be null."); } if(alphaBand == null) { throw new IllegalArgumentException("Alpha band cannot be null."); } // TODO: The bitwise calc functionality needs to be verified from the python functionality final boolean bitwiseAlpha = (alphaBand.GetMaskFlags() & gdalconstConstants.GMF_ALPHA) != 0; return bitwiseAlpha || dataset.GetRasterCount() == 4 || dataset.GetRasterCount() == 2 ? dataset.GetRasterCount() - 1 : dataset.GetRasterCount(); } /** * Get the index of the alpha {@link Band} of a Dataset, if any. * * @param dataset An input {@link Dataset} to search for an alpha {@link Band} * @return The index of the alpha band of the input Dataset if found * @throws TileStoreException Thrown when no alpha band could be detected. */ public static int getAlphaBandIndex(final Dataset dataset) throws TileStoreException { if(dataset == null) { throw new IllegalArgumentException("Input dataset cannot be null."); } return IntStream.rangeClosed(1, dataset.GetRasterCount()) .filter(index -> dataset.GetRasterBand(index).GetColorInterpretation() == gdalconstConstants.GCI_AlphaBand) .findFirst() .orElseThrow( () -> new TileStoreException("No Alpha band detected. Call getAlphaBandIndex after correcting nodata color.")); } /** * Correct an input raster {@link Dataset}s NODATA values to an alpha {@link Band} * * @param dataset An input {@link Dataset} * @return A dataset with an alpha band added that reflects the input Dataset's NODATA value */ public static Dataset correctNoDataSimple(final Dataset dataset) { if(dataset == null) { throw new IllegalArgumentException("Input dataset cannot be null."); } final boolean datasetHasAlphaBand = GdalUtility.hasAlpha(dataset); // If the dataset actually has an alpha band, return it if(datasetHasAlphaBand) { return dataset; } // Dataset has no alpha and is NOT a VRT if(!dataset.GetDriver().getShortName().equalsIgnoreCase("VRT")) { // Create a vrt of this dataset final Dataset vrtCopy = gdal.AutoCreateWarpedVRT(dataset); // Add an alpha band // TODO: This does not work even on VRT datasets. Find out why. vrtCopy.AddBand(gdalconstConstants.GDT_Byte); // A new band added is always the last, per docs vrtCopy.GetRasterBand(vrtCopy.GetRasterCount() + 1).SetColorInterpretation( gdalconstConstants.GCI_AlphaBand); return vrtCopy; } // Dataset has no alpha and IS a VRT dataset.AddBand(gdalconstConstants.GDT_Byte); dataset.GetRasterBand(dataset.GetRasterCount() + 1).SetColorInterpretation(gdalconstConstants.GCI_AlphaBand); return dataset; } /** * Return whether or not the input Dataset has an alpha {@link Band} * * @param dataset An input {@link Dataset} * @return True if the input {@link Dataset} has an alpha {@link Band}, * false otherwise. */ public static boolean hasAlpha(final Dataset dataset) { if(dataset == null) { throw new IllegalArgumentException("Input dataset cannot be null."); } return IntStream.rangeClosed(1, dataset.GetRasterCount()) .anyMatch(index -> dataset.GetRasterBand(index).GetColorInterpretation() == gdalconstConstants.GCI_AlphaBand); } /** * Create a set of GDAL parameters for reading tile data and writing that * data to another Dataset. * * @param geoTransform An array of doubles representing the geotransform of the input dataset * @param boundingBox The {@link BoundingBox} of the tile query * @param dimensions The tile {@link Dimensions} * @param dataset The input {@link Dataset} * @return An object with all information necessary to perform GDAL ReadRaster and WriteRaster * operations. */ public static GdalRasterParameters getGdalRasterParameters(final double[] geoTransform, final BoundingBox boundingBox, final Dimensions dimensions, final Dataset dataset) { if(geoTransform.length == 0) { throw new IllegalArgumentException("Geotransform cannot be empty."); } if(boundingBox == null) { throw new IllegalArgumentException("Bounding box cannot be null."); } if(dimensions == null) { throw new IllegalArgumentException("Tile dimensions cannot be null."); } if(dataset == null) { throw new IllegalArgumentException("Input dataset cannot be null."); } // This is sorcery of the darkest kind. It works but it not fully understood. final int readX = (int)((boundingBox.getMinimumX() - geoTransform[0]) / geoTransform[1] + 0.001); final int readY = (int)((boundingBox.getMaximumY() - geoTransform[3]) / geoTransform[5] + 0.001); final int readXSize = (int)(boundingBox.getWidth() / geoTransform[1] + 0.5); final int readYSize = (int)(boundingBox.getHeight() / -geoTransform[5] + 0.5); return new GdalRasterParameters(readX, readY, readXSize, readYSize, dimensions, dataset); } /** * Read a subset of image data from a raster {@link Dataset}. * * @param params A {@link GdalRasterParameters} object containing data on how * the tile should be read from the raster image * @param dataset The {@link Dataset} to read the tile data from * @return A {@link Byte} array of size @params.writeXSize() * * @throws TilingException Thrown when ReadRaster reports a failure * @throws IOException when {@link Dataset#ReadRaster} fails * @params.writeYSize() * @dataset.GetRasterCount() containing * tile data for the area specified in @params */ public static byte[] readRaster(final GdalRasterParameters params, final Dataset dataset) throws TilingException, IOException { if(params == null) { throw new IllegalArgumentException("GDAL parameters cannot be null."); } if(dataset == null) { throw new IllegalArgumentException("Input dataset cannot be null."); } final int bandCount = dataset.GetRasterCount(); // correctNoDataSimple should have added an alpha band if(params.getWriteXSize() < 0 || params.getWriteYSize() < 0) { throw new IOException("Tile call is outside the raster boundaries."); } final byte[] imageData = new byte[params.getWriteXSize() * params.getWriteYSize() * bandCount]; final int result = dataset.ReadRaster(params.getReadX(), // xOffset params.getReadY(), // yOffset params.getReadXSize(), // xSize params.getReadYSize(), // ySize params.getWriteXSize(), // buffer_xSize params.getWriteYSize(), // buffer_ySize gdalconstConstants.GDT_Byte, // buffer type imageData, // array into which the data will be written, must contain at least buffer_xSize * buffer_ySize * nBandCount null); // Per documentation, will select the first nBandCount bands if(result == gdalconstConstants.CE_Failure) { throw new IOException("Tile call outside of raster bounds."); } if(result == gdalconstConstants.CE_Fatal) { throw new TilingException("Fatal error detected from GDAL readRaster."); } return imageData; } /** * Read a subset of image data from a raster {@link Dataset} directly using a {@link ByteBuffer}. * * @param params A GDAL parameters object containing data on how the tile should be read from * the raster image * @param dataset The {@link Dataset} to read the tile data from * @return A {@link Byte} array of size @params.writeXSize() * @params.writeYSize() * @dataset.GetRasterCount() * containing tile data for the area specified in @params * @throws TilingException Thrown when ReadRaster_Direct reports a failure */ public static ByteBuffer readRasterDirect(final GdalRasterParameters params, final Dataset dataset) throws TilingException { if(params == null) { throw new IllegalArgumentException("GDAL parameters cannot be null."); } if(dataset == null) { throw new IllegalArgumentException("Input dataset cannot be null."); } final int bandCount = dataset.GetRasterCount(); // correctNoDataSimple should have added an alpha band final ByteBuffer imageData = ByteBuffer.allocateDirect(params.getWriteXSize() * params.getWriteYSize() * bandCount); final int result = dataset.ReadRaster_Direct(params.getReadX(), params.getReadY(), params.getReadXSize(), params.getReadYSize(), params.getWriteXSize(), params.getWriteYSize(), gdalconstConstants.GDT_Byte, imageData, null); // Per documentation, will select the first nBandCount bands if(result != gdalconstConstants.CE_None) { throw new TilingException("Failure reported by ReadRaster call in GdalUtility."); } return imageData; } /** * Write tile data to an output {@link Dataset}. * * @param params The {@link GdalRasterParameters} containing data on how the tile should be * written to the returned {@link Dataset} * @param imageData A {@link Byte} array of size @params.writeXSize() * @params.writeYSize() * @dataset.GetRasterCount() * containing tile data for the area specified * @param bandCount The number of bands the output {@link Dataset} should have * @return A {@link Dataset} representing a tile image * @throws TilingException Thrown when WriteRaster reports a failure */ public static Dataset writeRaster(final GdalRasterParameters params, final byte[] imageData, final int bandCount) throws TilingException { if(params == null) { throw new IllegalArgumentException("GDAL parameters cannot be null."); } if(imageData.length == 0) { throw new IllegalArgumentException("Image data must be non-zero length."); } final Dataset querySizeDatasetInMemory = gdal.GetDriverByName("MEM").Create("", params.getQueryXSize(), params.getQueryYSize(), bandCount); final int result = querySizeDatasetInMemory.WriteRaster(params.getWriteX(), params.getWriteY(), params.getWriteXSize(), params.getWriteYSize(), params.getWriteXSize(), params.getWriteYSize(), gdalconstConstants.GDT_Byte, imageData, null); // Per documentation, will select the first nBandCount bands if(result != gdalconstConstants.CE_None) { throw new TilingException("Failure reported by WriteRaster call in GdalUtility."); } return querySizeDatasetInMemory; } /** * Write tile data to an output {@link Dataset} directly using a {@link ByteBuffer}. * * @param params The {@link GdalRasterParameters} containing data on how the tile should be * written to the returned {@link Dataset} * @param imageData A {@link Byte} array of size @params.writeXSize() * @params.writeYSize() * @dataset.GetRasterCount() * containing tile data for the area specified * @param bandCount The number of bands the output {@link Dataset} should have * @return A {@link Dataset} representing a tile image * @throws TilingException Thrown when WriteRaster_Direct reports a failure */ public static Dataset writeRasterDirect(final GdalRasterParameters params, final ByteBuffer imageData, final int bandCount) throws TilingException { if(params == null) { throw new IllegalArgumentException("GDAL parameters cannot be null."); } if(imageData == null) { throw new IllegalArgumentException("Image data must be non-zero length."); } final Dataset querySizeDatasetInMemory = gdal.GetDriverByName("MEM").Create("", params.getQueryXSize(), params.getQueryYSize(), bandCount); final int result = querySizeDatasetInMemory.WriteRaster_Direct(params.getWriteX(), params.getWriteY(), params.getWriteXSize(), params.getWriteYSize(), params.getWriteXSize(), params.getWriteYSize(), gdalconstConstants.GDT_Byte, imageData, null); // Per documentation, will select the first nBandCount bands if(result != gdalconstConstants.CE_None) { throw new TilingException("Failure reported by WriteRasterDirect call in GdalUtility."); } return querySizeDatasetInMemory; } /** * An object containing all data necessary for GDAL ReadRaster and WriteRaster functions. * * @author Steven D. Lander */ public static class GdalRasterParameters { private int readX; private int readY; private int readXSize; private int readYSize; private int writeX; private int writeY; private int writeXSize; private int writeYSize; private final int queryXSize; private final int queryYSize; /** * @param readX The X-axis pixel location to start reading tile data from * @param readY The Y-axis pixel location to start reading tile data from * @param readXSize The amount of pixels to read in the X-axis * @param readYSize The amount of pixels to read in the Y-axis * @param dimensions The {@link Dimensions} of the tile grid * @param dataset The raster {@link Dataset} that is being manipulated */ public GdalRasterParameters(final int readX, final int readY, final int readXSize, final int readYSize, final Dimensions dimensions, final Dataset dataset) { if(dimensions == null) { throw new IllegalArgumentException("Dimensions of the tile system cannot be null."); } if(dataset == null) { throw new IllegalArgumentException("Input dataset must be supplied to GdalRasterParameters."); } // Points that dictate where a tile read occurs on the dataset this.readX = readX; this.readY = readY; // Size values that dictate how much data should be read from the dataset for // a tile read operation this.readXSize = readXSize; this.readYSize = readYSize; // Points on the write canvas that dictate where the read data should be written this.writeX = 0; this.writeY = 0; // Size values that indicate how large the tile query canvas should be // Hard coding the query to be larger size for later down-scaling this.queryXSize = 4 * dimensions.getWidth(); this.queryYSize = 4 * dimensions.getHeight(); // Size values that dictate how large the write canvas should be this.writeXSize = this.queryXSize; this.writeYSize = this.queryYSize; this.adjust(dataset); } /** * @return The point in the x axis where tile data should be read from */ public int getReadX() { return this.readX; } /** * @return The point in the x axis where tile data should be read from */ public int getReadY() { return this.readY; } /** * @return The raster read size for the x axis */ public int getReadXSize() { return this.readXSize; } /** * @return The raster read size for the y axis */ public int getReadYSize() { return this.readYSize; } /** * @return The point in the x axis where tile data should be written */ public int getWriteX() { return this.writeX; } /** * @return The point in the y axis where tile data should be written */ public int getWriteY() { return this.writeY; } /** * @return The raster canvas write size for the x axis */ public int getWriteXSize() { return this.writeXSize; } /** * @return The raster canvas write size for the y axis */ public int getWriteYSize() { return this.writeYSize; } /** * @return The size of the raster data query in the x axis */ public int getQueryXSize() { return this.queryXSize; } /** * @return The size of the raster data query in the y axis */ public int getQueryYSize() { return this.queryYSize; } /** * Adjust final read, write, and size parameters for a GDAL calls to ReadRaster and * Write Raster. * * @param dataset The input dataset with which all values will be adjusted from */ private void adjust(final Dataset dataset) { if(this.readX < 0) { final int readXShift = Math.abs(this.readX); this.writeX = (int)(this.writeXSize * ((float)readXShift / this.readXSize)); this.writeXSize -= this.writeX; this.readXSize -= (int)(this.readXSize * ((float)readXShift) / this.readXSize); this.readX = 0; } if(this.readX + this.readXSize > dataset.GetRasterXSize()) { this.writeXSize = (int)(this.writeXSize * ((float)(dataset.GetRasterXSize() - this.readX) / this.readXSize)); this.readXSize = dataset.GetRasterXSize() - this.readX; } if(this.readY < 0) { final int readYShift = Math.abs(this.readY); this.writeY = (int)(this.writeYSize * ((float)readYShift / this.readYSize)); this.writeYSize -= this.writeY; this.readYSize -= (int)(this.readYSize * ((float)readYShift / this.readYSize)); this.readY = 0; } if(this.readY + this.readYSize > dataset.GetRasterYSize()) { this.writeYSize = (int)(this.writeYSize * ((float)(dataset.GetRasterYSize() - this.readY) / this.readYSize)); this.readYSize = dataset.GetRasterYSize() - this.readY; } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy