public void ProjectionKauai()
        {
            var definition = SatelliteRegistry.Locate(Goes17DefinitionPrefix);

            Assert.NotNull(definition, "Unable to find satellite definition");

            var imageOffset = Constants.Satellite.Offset.TwoKm;

            const int x = 1614;
            const int y = 1564;

            // Convert from scanning angle to geodetic
            var scanningX = imageOffset.ToHorizontalScanningAngle(x);
            var scanningY = imageOffset.ToVerticalScanningAngle(y);

            ReverseGeostationaryProjection.ToLatitudeLongitude(scanningX, scanningY, definition.Longitude, definition.Height, out var latitude, out var longitude);

            FromRadians(latitude).Degrees.Should().BeApproximately(21.869911403254573, Precision);
            FromRadians(longitude).Degrees.Should().BeApproximately(-159.66206692006023, Precision);

            // Convert back to scanning angle
            GeostationaryProjection.ToScanningAngle(latitude, longitude, definition, out var scanningXResult, out var scanningYResult);
            scanningXResult.Should().BeApproximately(scanningX, Precision);
            scanningYResult.Should().BeApproximately(scanningY, Precision);

            imageOffset.ToImageCoordinates(scanningXResult, scanningYResult, out var targetX, out var targetY);

            Math.Round(targetX).Should().BeApproximately(x, PixelPrecision);
            Math.Round(targetY).Should().BeApproximately(y, PixelPrecision);
        }
        public void ProjectionLakeTaupo()
        {
            var definition = SatelliteRegistry.Locate(Goes17DefinitionPrefix);

            Assert.NotNull(definition, "Unable to find satellite definition");

            var imageOffset = Constants.Satellite.Offset.TwoKm;

            const int x = 1050;
            const int y = 4533;

            // Convert from scanning angle to geodetic
            var scanningX = imageOffset.ToHorizontalScanningAngle(x);
            var scanningY = imageOffset.ToVerticalScanningAngle(y);

            ReverseGeostationaryProjection.ToLatitudeLongitude(scanningX, scanningY, definition.Longitude, definition.Height, out var latitude, out var longitude);

            FromRadians(latitude).Degrees.Should().BeApproximately(-38.70625734950147, Precision);
            FromRadians(longitude).Degrees.Should().BeApproximately(175.9626023681634, Precision);

            // Convert back to scanning angle
            GeostationaryProjection.ToScanningAngle(latitude, longitude, definition, out var scanningXResult, out var scanningYResult);
            scanningXResult.Should().BeApproximately(scanningX, Precision);
            scanningYResult.Should().BeApproximately(scanningY, Precision);

            imageOffset.ToImageCoordinates(scanningXResult, scanningYResult, out var targetX, out var targetY);

            Math.Round(targetX).Should().BeApproximately(x, PixelPrecision);
            Math.Round(targetY).Should().BeApproximately(y, PixelPrecision);
        }
        public void ProjectionCalifornia()
        {
            var definition = SatelliteRegistry.Locate(Goes17DefinitionPrefix);

            Assert.NotNull(definition, "Unable to find satellite definition");

            var imageOffset = Constants.Satellite.Offset.TwoKm;

            const int x = 3717;
            const int y = 1275;

            // Convert from scanning angle to geodetic
            var scanningX = imageOffset.ToHorizontalScanningAngle(x);
            var scanningY = imageOffset.ToVerticalScanningAngle(y);

            ReverseGeostationaryProjection.ToLatitudeLongitude(scanningX, scanningY, definition.Longitude, definition.Height, out var latitude, out var longitude);

            FromRadians(latitude).Degrees.Should().BeApproximately(28.007393788242464, Precision);
            FromRadians(longitude).Degrees.Should().BeApproximately(-115.44783176403854, Precision);

            // Convert back to scanning angle
            GeostationaryProjection.ToScanningAngle(latitude, longitude, definition, out var scanningXResult, out var scanningYResult);
            scanningXResult.Should().BeApproximately(scanningX, Precision);
            scanningYResult.Should().BeApproximately(scanningY, Precision);

            imageOffset.ToImageCoordinates(scanningXResult, scanningYResult, out var targetX, out var targetY);

            Math.Round(targetX).Should().BeApproximately(x, PixelPrecision);
            Math.Round(targetY).Should().BeApproximately(y, PixelPrecision);
        }
        /// <summary>
        ///     Performs latitude calculations as part of lat/long to scanning angle. These are cached in order to avoid
        ///     repeating latitude calculations for every pixel.
        /// </summary>
        private LatitudeCalculations CalculateGeostationaryLatitude(int y)
        {
            var target  = _target;
            var yOffset = _yOffset;

            return(LatitudeCalculationCache.GetOrAdd(y, angle =>
            {
                // Convert pixel row to latitude
                var projectionY = ProjectionAngleConverter.FromY(y, target.Height + yOffset * 2);

                // Perform and cache intermediary geostationary latitude calculations
                return GeostationaryProjection.LatitudeCalculations(projectionY);
            }));
        }
        public void TextbookToScanningAngle()
        {
            var(definition, _) = SatelliteRegistry.Locate(Goes16DefinitionPrefix);
            Assert.NotNull(definition, "Unable to find satellite definition");

            GeostationaryProjection.ToScanningAngle(
                FromDegrees(33.846162).Radians,
                FromDegrees(-84.690932).Radians,
                definition !,
                out var scanningX,
                out var scanningY);

            scanningX.Should().BeApproximately(-0.023807, Precision);
            scanningY.Should().BeApproximately(0.0953439, Precision);
        }
        public void Invoke(int y)
        {
            var span = _target.GetPixelRowSpan(y);

            // Calculate or retrieve the latitude calculation component of geostationary projection
            var latitudeCalculations = CalculateGeostationaryLatitude(y + _yOffset);

            // Convert image x,y to Mercator projection angle
            var targetWidth = _registration.Width * 2;
            var projectionY = ProjectionAngleConverter.FromY(y + _yOffset, _registration.Height);

            for (var x = 0; x < span.Length; x++)
            {
                var projectionX = ProjectionAngleConverter.FromX(x + _xOffset, targetWidth);

                // Convert latitude/longitude to geostationary scanning angle
                GeostationaryProjection.ToScanningAngle(latitudeCalculations, projectionX, _registration.Definition, out var scanningX, out var scanningY);

                // Map pixel from satellite image back to target image
                span[x] = GetTargetColour(scanningX, scanningY, projectionY, projectionX);
            }
        }