Gigas002/GTiff2Tiles

View on GitHub
GTiff2Tiles.Core/Images/Area.cs

Summary

Maintainability
A
0 mins
Test Coverage
using GTiff2Tiles.Core.Coordinates;
using GTiff2Tiles.Core.GeoTiffs;
using GTiff2Tiles.Core.Localization;
using GTiff2Tiles.Core.Tiles;

// ReSharper disable MemberCanBePrivate.Global

namespace GTiff2Tiles.Core.Images;

/// <summary>
/// Represents read/write <see cref="Area"/>s of image
/// </summary>
public class Area
{
    #region Properties

    /// <summary>
    /// Origin <see cref="PixelCoordinate"/>
    /// </summary>
    public PixelCoordinate OriginCoordinate { get; }

    /// <summary>
    /// <see cref="Images.Size"/> of <see cref="Area"/>
    /// </summary>
    public Size Size { get; }

    #endregion

    #region Constructors

    /// <summary>
    /// Creates new <see cref="Area"/>
    /// </summary>
    /// <param name="originCoordinate"><see cref="OriginCoordinate"/></param>
    /// <param name="size"><see cref="Size"/></param>
    /// <exception cref="ArgumentNullException"/>
    public Area(PixelCoordinate originCoordinate, Size size)
    {
        #region Preconditions checks

        if (originCoordinate == null) throw new ArgumentNullException(nameof(originCoordinate));
        if (size == null) throw new ArgumentNullException(nameof(size));

        #endregion

        (OriginCoordinate, Size) = (originCoordinate, size);
    }

    #endregion

    #region Methods

    /// <inheritdoc cref="GetAreas(IGeoTiff, ITile)"/>
    /// <param name="imageMinCoordinate">Minimal <see cref="GeoCoordinate"/>
    /// of <see cref="IGeoTiff"/></param>
    /// <param name="imageMaxCoordinate">Maximal <see cref="GeoCoordinate"/>
    /// of <see cref="IGeoTiff"/></param>
    /// <param name="imageSize"><see cref="Images.Size"/> of <see cref="IGeoTiff"/></param>
    /// <param name="tileMinCoordinate">Minimal <see cref="GeoCoordinate"/>
    /// of <see cref="ITile"/></param>
    /// <param name="tileMaxCoordinate">Maximal <see cref="GeoCoordinate"/>
    /// of <see cref="ITile"/></param>
    /// <param name="tileSize"><see cref="Images.Size"/> of <see cref="ITile"/></param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException"/>
    /// <exception cref="ArgumentException"/>
    public static (Area readArea, Area writeArea)? GetAreas(GeoCoordinate imageMinCoordinate,
                                                           GeoCoordinate imageMaxCoordinate, Size imageSize,
                                                           GeoCoordinate tileMinCoordinate,
                                                           GeoCoordinate tileMaxCoordinate, Size tileSize)
    {
        #region Preconditions checks

        if (imageMinCoordinate == null) throw new ArgumentNullException(nameof(imageMinCoordinate));
        if (imageMaxCoordinate == null) throw new ArgumentNullException(nameof(imageMaxCoordinate));

        string err = string.Format(Strings.Culture, Strings.Equal, nameof(imageMinCoordinate), nameof(imageMaxCoordinate));

        // This is to prevent unclear DivideByZeroException exception
        if (imageMinCoordinate == imageMaxCoordinate)
            throw new ArgumentException(err);

        if (imageSize == null) throw new ArgumentNullException(nameof(imageSize));
        if (tileMinCoordinate == null) throw new ArgumentNullException(nameof(tileMinCoordinate));
        if (tileMaxCoordinate == null) throw new ArgumentNullException(nameof(tileMaxCoordinate));

        err = string.Format(Strings.Culture, Strings.Equal, nameof(tileMinCoordinate), nameof(tileMaxCoordinate));

        // This is to prevent unclear DivideByZeroException exception
        if (tileMinCoordinate == tileMaxCoordinate)
            throw new ArgumentException(err);

        if (tileSize == null) throw new ArgumentNullException(nameof(tileSize));

        #endregion

        // Read from input geotiff in pixels
        double readPosMinX = imageSize.Width * (tileMinCoordinate.X - imageMinCoordinate.X) / (imageMaxCoordinate.X - imageMinCoordinate.X);
        double readPosMaxX = imageSize.Width * (tileMaxCoordinate.X - imageMinCoordinate.X) / (imageMaxCoordinate.X - imageMinCoordinate.X);
        double readPosMinY = imageSize.Height - imageSize.Height * (tileMaxCoordinate.Y - imageMinCoordinate.Y) / (imageMaxCoordinate.Y - imageMinCoordinate.Y);
        double readPosMaxY = imageSize.Height - imageSize.Height * (tileMinCoordinate.Y - imageMinCoordinate.Y) / (imageMaxCoordinate.Y - imageMinCoordinate.Y);

        // If outside of tiff -- set to 0.0/Max
        readPosMinX = readPosMinX < 0.0 ? 0.0 : readPosMinX > imageSize.Width ? imageSize.Width : readPosMinX;
        readPosMaxX = readPosMaxX < 0.0 ? 0.0 : readPosMaxX > imageSize.Width ? imageSize.Width : readPosMaxX;
        readPosMinY = readPosMinY < 0.0 ? 0.0 : readPosMinY > imageSize.Height ? imageSize.Height : readPosMinY;
        readPosMaxY = readPosMaxY < 0.0 ? 0.0 : readPosMaxY > imageSize.Height ? imageSize.Height : readPosMaxY;

        // Output tile's borders in pixels
        double tilePixMinX = readPosMinX.Equals(0.0) ? imageMinCoordinate.X :
                             readPosMinX.Equals(imageSize.Width) ? imageMaxCoordinate.X : tileMinCoordinate.X;
        double tilePixMaxX = readPosMaxX.Equals(0.0) ? imageMinCoordinate.X :
                             readPosMaxX.Equals(imageSize.Width) ? imageMaxCoordinate.X : tileMaxCoordinate.X;
        double tilePixMinY = readPosMaxY.Equals(0.0) ? imageMaxCoordinate.Y :
                             readPosMaxY.Equals(imageSize.Height) ? imageMinCoordinate.Y : tileMinCoordinate.Y;
        double tilePixMaxY = readPosMinY.Equals(0.0) ? imageMaxCoordinate.Y :
                             readPosMinY.Equals(imageSize.Height) ? imageMinCoordinate.Y : tileMaxCoordinate.Y;


        // Positions of dataset to write in tile
        double writePosMinX = tileSize.Width - tileSize.Width * (tileMaxCoordinate.X - tilePixMinX) / (tileMaxCoordinate.X - tileMinCoordinate.X);
        double writePosMaxX = tileSize.Width - tileSize.Width * (tileMaxCoordinate.X - tilePixMaxX) / (tileMaxCoordinate.X - tileMinCoordinate.X);
        double writePosMinY = tileSize.Height * (tileMaxCoordinate.Y - tilePixMaxY) / (tileMaxCoordinate.Y - tileMinCoordinate.Y);
        double writePosMaxY = tileSize.Height * (tileMaxCoordinate.Y - tilePixMinY) / (tileMaxCoordinate.Y - tileMinCoordinate.Y);

        // Sizes to read and write
        double readXSize = readPosMaxX - readPosMinX;
        double writeXSize = writePosMaxX - writePosMinX;
        double readYSize = Math.Abs(readPosMaxY - readPosMinY);
        double writeYSize = Math.Abs(writePosMaxY - writePosMinY);

        // Shifts
        double readXShift = readPosMinX - (int)readPosMinX;
        readXSize += readXShift;
        double readYShift = readPosMinY - (int)readPosMinY;
        readYSize += readYShift;
        double writeXShift = writePosMinX - (int)writePosMinX;
        writeXSize += writeXShift;
        double writeYShift = writePosMinY - (int)writePosMinY;
        writeYSize += writeYShift;

        // If output image sides are lesser then 1 - make image 1x1 pixels to prevent division by 0
        writeXSize = writeXSize > 1.0 ? writeXSize : 1.0;
        writeYSize = writeYSize > 1.0 ? writeYSize : 1.0;

        PixelCoordinate readOriginCoordinate = new(readPosMinX, readPosMinY);
        PixelCoordinate writeOriginCoordinate = new(writePosMinX, writePosMinY);

        if (readXSize < 1 || readYSize < 1 || writeXSize < 1 || writeYSize < 1) return null;
        
        Size readSize = new((int)readXSize, (int)readYSize);
        Size writeSize = new((int)writeXSize, (int)writeYSize);

        Area readArea = new(readOriginCoordinate, readSize);
        Area writeArea = new(writeOriginCoordinate, writeSize);

        return (readArea, writeArea);
    }

    /// <summary>
    /// Get <see cref="Area"/>s to read from input <see cref="IGeoTiff"/>
    /// and to write to target <see cref="ITile"/>
    /// </summary>
    /// <param name="image">Source <see cref="IGeoTiff"/></param>
    /// <param name="tile">Target <see cref="ITile"/></param>
    /// <returns><see cref="ValueTuple{T1, T2}"/> of <see cref="Area"/>s to read and write</returns>
    /// <exception cref="ArgumentNullException"/>
    public static (Area readArea, Area writeArea)? GetAreas(IGeoTiff image, ITile tile)
    {
        #region Preconditions checks

        if (image == null) throw new ArgumentNullException(nameof(image));
        if (tile == null) throw new ArgumentNullException(nameof(tile));

        #endregion

        return GetAreas(image.MinCoordinate, image.MaxCoordinate, image.Size, tile.MinCoordinate,
                        tile.MaxCoordinate, tile.Size);
    }

    #endregion
}