예제 #1
0
        public async Task GeostationaryUnderlay()
        {
            var definition = SatelliteRegistry.Locate(Goes16DefinitionPrefix);

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

            var options = new UnderlayProjectionData(
                ProjectionType.Geostationary,
                InterpolationType.NearestNeighbour, "underlay.jpg", 5424, 1000);

            var underlay = await UnderlayService.GetUnderlayAsync(options, definition);

            underlay.Width.Should().Be(1000);
            underlay.Height.Should().Be(1000);

            // Retrieve cached
            underlay = await UnderlayService.GetUnderlayAsync(options, definition);

            underlay.Width.Should().Be(1000);
            underlay.Height.Should().Be(1000);

            // Verify changing options doesn't retrieve cached underlay
            options = new UnderlayProjectionData(
                ProjectionType.Geostationary,
                InterpolationType.NearestNeighbour, "underlay.jpg", 5424, 1500);

            underlay = await UnderlayService.GetUnderlayAsync(options, definition);

            underlay.Width.Should().Be(1500);
            underlay.Height.Should().Be(1500);
        }
예제 #2
0
        /// <summary>
        ///     Optionally resizes the underlay based on the target height.
        /// </summary>
        private void Resize(UnderlayProjectionData data, Image <Rgba32> underlay)
        {
            if (data.TargetSize == null)
            {
                return;
            }

            // Resize underlay to target image size
            underlay.Mutate(c => c.Resize(data.TargetSize.Value));
        }
예제 #3
0
        public async Task EquirectangularUnderlay()
        {
            var definition = SatelliteRegistry.Locate(Goes16DefinitionPrefix);

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

            var options = new UnderlayProjectionData(
                ProjectionType.Equirectangular,
                InterpolationType.NearestNeighbour,
                "underlay.jpg",
                5424);

            var underlay = await UnderlayService.GetUnderlayAsync(options, definition);

            underlay.Width.Should().Be(10848);
            underlay.Height.Should().Be(5424);
        }
예제 #4
0
        /// <summary>
        ///     Optionally resizes the underlay based on the target height.
        /// </summary>
        private void Resize(UnderlayProjectionData data, Image <Rgba32> underlay)
        {
            if (data.TargetHeight == null)
            {
                return;
            }

            // Resize underlay to target image size
            var targetHeight = data.TargetHeight.Value;

            // Ensure correct aspect ratio
            var targetWidth = (int)Math.Round(underlay.Width / (float)underlay.Height * targetHeight);

            _logger.LogInformation("Resizing underlay to {width} x {height} px", targetWidth, targetHeight);

            underlay.Mutate(c => c.Resize(targetWidth, targetHeight));
        }
예제 #5
0
        public async Task EquirectangularUnderlayWithCrop()
        {
            var(definition, _) = SatelliteRegistry.Locate(Goes16DefinitionPrefix);
            Assert.NotNull(definition, "Unable to find satellite definition");

            var options = new UnderlayProjectionData(
                ProjectionType.Equirectangular,
                InterpolationType.NearestNeighbour,
                "underlay.jpg",
                5424,
                latitudeCrop: new Range(Angle.FromDegrees(45), Angle.FromDegrees(-45)));

            RenderOptions.EquirectangularRender = new EquirectangularRenderOptions(false, false, false);
            var underlay = await UnderlayService.GetUnderlayAsync(options, definition);

            underlay.Width.Should().Be(10848);
            underlay.Height.Should().Be(2712);
        }
예제 #6
0
        /// <summary>
        ///     Adds an underlay registration to the database.
        /// </summary>
        /// <param name="definition">optional satellite definition used to generate underlay</param>
        /// <param name="data">underlay rendering options</param>
        /// <param name="underlayPath">path to rendered underlay</param>
        public async Task RegisterCacheAsync(SatelliteDefinition?definition, UnderlayProjectionData data, string underlayPath)
        {
            await using var connection = new SqliteConnection(ConnectionString);
            await connection.OpenAsync();

            string sql = definition == null
                ? "INSERT INTO UnderlayCache(Filename, Configuration, Timestamp) VALUES(@Filename, @Configuration, @Timestamp)"
                : "INSERT INTO UnderlayCache(Filename, Configuration, Longitude, Timestamp) VALUES(@Filename, @Configuration, @Longitude, @Timestamp)";

            var timestamp = File.GetLastWriteTimeUtc(underlayPath);
            await connection.ExecuteAsync(sql, new
            {
                Filename      = underlayPath,
                Configuration = JsonConvert.SerializeObject(data),
                Longitude     = definition?.Longitude,
                Timestamp     = (DateTimeOffset)timestamp
            });
        }
예제 #7
0
        public async Task EquirectangularUnderlayNoCrop()
        {
            var(definition, _) = SatelliteRegistry.Locate(Goes16DefinitionPrefix);
            Assert.NotNull(definition, "Unable to find satellite definition");

            var data = new UnderlayProjectionData(
                ProjectionType.Equirectangular,
                InterpolationType.NearestNeighbour,
                "underlay.jpg",
                1000,
                new Size(5424, 5424),
                new Range(0, Math.PI / 2));

            RenderOptions.EquirectangularRender = new EquirectangularRenderOptions(false, true, false);
            var underlay = await UnderlayService.GetUnderlayAsync(data, definition);

            underlay.Width.Should().Be(5424);
            underlay.Height.Should().Be(5424);
        }
예제 #8
0
        /// <summary>
        ///     Retrieves a full-colour underlay image with the target projection. Underlays are cached to disk to speed up
        ///     computation.
        /// </summary>
        /// <param name="data">Underlay generation options</param>
        /// <param name="definition">Optional satellite definition, if projecting underlay to match a satellite IR image</param>
        /// <returns>projected underlay</returns>
        public async Task <Image <Rgba32> > GetUnderlayAsync(UnderlayProjectionData data, SatelliteDefinition?definition = null)
        {
            // Attempt to retrieve underlay from cache
            var cached = await _cache.GetUnderlayAsync(definition, data);

            if (cached != null)
            {
                return(cached);
            }

            // Load master equirectangular underlay image from disk
            var underlay = await Image.LoadAsync <Rgba32>(_options.UnderlayPath);

            // Project to match satellite imagery
            var target = GetProjected(underlay, definition, data);

            Resize(data, target);

            // Register underlay in cache
            await _cache.SetUnderlayAsync(target, definition, data);

            return(target);
        }
예제 #9
0
        public override async Task <ExecutionResult> RunAsync(IStepExecutionContext context)
        {
            Guard.Against.Null(_options.GeostationaryRender, nameof(_options.GeostationaryRender));
            Guard.Against.Null(Registration?.Image, nameof(Registration.Image));

            if (_options.NoUnderlay)
            {
                TargetImage = Registration.Image.Clone();
                return(ExecutionResult.Next());
            }

            // Get or generate projected underlay
            var underlayOptions = new UnderlayProjectionData(
                ProjectionType.Geostationary,
                _options.InterpolationType,
                _options.UnderlayPath,
                _options.ImageSize);

            _logger.LogInformation("Retrieving underlay");
            var underlay = await _underlayService.GetUnderlayAsync(underlayOptions, Registration.Definition);

            _logger.LogInformation("Tinting and normalising IR imagery");
            if (_options.AutoAdjustLevels)
            {
                Registration.Image.AdjustLevels();
            }

            TargetImage = Registration.Image.Clone();
            TargetImage.Tint(_options.Tint);

            _logger.LogInformation("Blending with underlay");
            TargetImage.Mutate(c => c
                               .Resize(_options.ImageSize, _options.ImageSize)
                               .DrawImage(underlay, PixelColorBlendingMode.Screen, 1.0f));

            return(ExecutionResult.Next());
        }
예제 #10
0
        /// <summary>
        ///     Retrieves an underlay image from the cache.
        /// </summary>
        public async Task <Image <Rgba32>?> GetUnderlayAsync(SatelliteDefinition?definition, UnderlayProjectionData data)
        {
            var metadata = await _repository.GetCacheMetadataAsync(definition, data);

            if (metadata == null)
            {
                return(null);
            }

            var path = metadata.Filename;

            var fullPath = Path.Combine(_cachePath, path);

            // Remove underlay registration from cache if it has been deleted from disk
            if (!File.Exists(fullPath))
            {
                _logger.LogWarning("Cache file {path} not found; removing from cache registration", path);
                await _repository.ClearCacheEntryAsync(path);
            }

            var fileTimestamp = File.GetLastWriteTimeUtc(_options.UnderlayPath);

            // Check if the source underlay file has been updated since the cache was created
            if (metadata.Timestamp < fileTimestamp)
            {
                _logger.LogInformation("Timestamp of underlay changed; updating cache");
                await _repository.ClearCacheEntryAsync(path);

                return(null);
            }

            try
            {
                if (definition == null)
                {
                    _logger.LogInformation("Using cached underlay");
                }
                else
                {
                    _logger.LogInformation("{definition:l0} Using cached underlay", definition.DisplayName);
                }

                return(await Image.LoadAsync <Rgba32>(fullPath));
            }
            catch (Exception e)
            {
                // Remove underlay registration from cache if there was an error reading it from disk
                _logger.LogWarning(e, "Cache file {path} unable to be read; removing from cache registration", path);
                await _repository.ClearCacheEntryAsync(path);
            }

            return(null);
        }
예제 #11
0
        /// <summary>
        ///     Sets an underlay image to the cache.
        /// </summary>
        public async Task SetUnderlayAsync(Image <Rgba32> underlay, SatelliteDefinition?definition, UnderlayProjectionData data)
        {
            if (definition == null)
            {
                _logger.LogInformation("Caching underlay");
            }
            else
            {
                _logger.LogInformation("{definition:l0} Caching underlay", definition.DisplayName);
            }

            // Save underlay to disk
            var filename     = $"{Guid.NewGuid()}.jpg";
            var underlayPath = Path.Combine(_cachePath, filename);
            await underlay.SaveAsync(underlayPath);

            // Register underlay path in the cache
            await _repository.RegisterCacheAsync(definition, data, underlayPath);
        }
예제 #12
0
        /// <summary>
        ///     Returns an underlay projected and optionally cropped.
        /// </summary>
        private Image <Rgba32> GetProjected(Image <Rgba32> underlay, SatelliteDefinition?definition, UnderlayProjectionData data)
        {
            switch (data.Projection)
            {
            case ProjectionType.Geostationary:
                if (definition == null)
                {
                    throw new InvalidOperationException("Satellite definition must be provided for geostationary projection");
                }

                // Project underlay to geostationary, based on the target satellite
                _logger.LogInformation("{definition:l0} Rendering geostationary underlay", definition.DisplayName);
                return(underlay.ToGeostationaryProjection(definition.Longitude, definition.Height, _options));

            case ProjectionType.Equirectangular:

                // Optionally crop and offset to specified lat/long range
                if (data.CropSpecified)
                {
                    Offset(underlay, data.LongitudeCrop !.Value);
                    Crop(underlay, data.LatitudeCrop !.Value);
                }

                return(underlay);

            default:
                throw new ArgumentOutOfRangeException($"Unhandled projection type: {data.Projection}");
            }
        }
예제 #13
0
        /// <summary>
        ///     Returns an underlay projected and optionally cropped.
        /// </summary>
        private Image <Rgba32> GetProjected(Image <Rgba32> underlay, SatelliteDefinition?definition, UnderlayProjectionData data)
        {
            switch (data.Projection)
            {
            case ProjectionType.Geostationary:
                if (definition == null)
                {
                    throw new InvalidOperationException("Satellite definition must be provided for geostationary projection");
                }

                // Project underlay to geostationary, based on the target satellite
                _logger.LogInformation("{Definition:l0} Rendering geostationary underlay", definition.DisplayName);
                return(underlay.ToGeostationaryProjection(definition.Longitude, definition.Height, _options));

            case ProjectionType.Equirectangular:

                // Perform latitude crop to match IR imagery if required
                var equirectangularOptions = _options.EquirectangularRender;

                if (equirectangularOptions == null || !equirectangularOptions.NoCrop && !equirectangularOptions.ExplicitCrop)
                {
                    Crop(underlay, data.LatitudeCrop !.Value);
                }

                return(underlay);

            default:
                throw new ArgumentOutOfRangeException($"Unhandled projection type: {data.Projection}");
            }
        }
예제 #14
0
        /// <summary>
        ///     Retrieves a cached underlay filename and timestamp based on an optional satellite definition and render options, or
        ///     <c>null</c> if the underlay isn't in the cache.
        /// </summary>
        public async Task <UnderlayMetadata?> GetCacheMetadataAsync(SatelliteDefinition?definition, UnderlayProjectionData data)
        {
            await using var connection = new SqliteConnection(ConnectionString);
            await connection.OpenAsync();

            var sql = "SELECT Filename, Timestamp FROM UnderlayCache WHERE Configuration=@Configuration";

            if (definition != null)
            {
                sql += " AND Longitude=@Longitude";
            }

            return(await connection.QueryFirstOrDefaultAsync <UnderlayMetadata>(sql, new
            {
                definition?.Longitude,
                Configuration = JsonConvert.SerializeObject(data)
            }));
        }