示例#1
0
        private static List <GeoTiff.TileCoordinates> BuildTileCoordinatesList(
            M.RasterProperties rasterProperties,
            M.Bounds bounds)
        {
            if (rasterProperties == null)
            {
                throw new ArgumentNullException(nameof(rasterProperties));
            }

            if (rasterProperties.ProjectedBounds == null)
            {
                throw new ArgumentException("ProjectedBounds property is null.");
            }

            var tileCoordMin = GetGeoTiffTileCoordinatesAtPoint(
                rasterProperties,
                Math.Max(bounds.Left, rasterProperties.ProjectedBounds.Left),
                Math.Min(bounds.Top, rasterProperties.ProjectedBounds.Top));

            var tileCoordMax = GetGeoTiffTileCoordinatesAtPoint(
                rasterProperties,
                Math.Min(bounds.Right, rasterProperties.ProjectedBounds.Right),
                Math.Min(bounds.Bottom, rasterProperties.ProjectedBounds.Bottom));

            var tileCoordinates = new List <GeoTiff.TileCoordinates>();

            for (var tileX = tileCoordMin.X; tileX <= tileCoordMax.X; tileX++)
            {
                for (var tileY = tileCoordMin.Y; tileY <= tileCoordMax.Y; tileY++)
                {
                    tileCoordinates.Add(new GeoTiff.TileCoordinates(tileX, tileY));
                }
            }

            return(tileCoordinates);
        }
示例#2
0
        private void DrawGeoTiffTilesToRasterCanvas(
            SKCanvas outputCanvas,
            int width, int height,
            M.Bounds tileBounds,
            IList <GeoTiff.TileCoordinates> sourceTileCoordinates,
            uint backgroundColor,
            int sourceTileWidth,
            int sourceTileHeight)
        {
            if (this.rasterProperties == null)
            {
                throw new InvalidOperationException("rasterProperties is null.");
            }

            if (String.IsNullOrEmpty(this.configuration.Location))
            {
                throw new InvalidOperationException("configuration.Location is null or empty");
            }

            var tileMinX     = sourceTileCoordinates.Min(t => t.X);
            var tileMinY     = sourceTileCoordinates.Min(t => t.Y);
            var tilesCountX  = sourceTileCoordinates.Max(t => t.X) - tileMinX + 1;
            var tilesCountY  = sourceTileCoordinates.Max(t => t.Y) - tileMinY + 1;
            var canvasWidth  = tilesCountX * sourceTileWidth;
            var canvasHeight = tilesCountY * sourceTileHeight;

            // TODO: ? scale before draw to reduce memory allocation
            // TODO: check max canvas size

            var imageInfo = new SKImageInfo(
                width: canvasWidth,
                height: canvasHeight,
                colorType: SKColorType.Rgba8888,
                alphaType: SKAlphaType.Premul);

            using var surface = SKSurface.Create(imageInfo);
            using var canvas  = surface.Canvas;
            canvas.Clear(new SKColor(backgroundColor));

            // Draw all source tiles without scaling
            foreach (var sourceTile in sourceTileCoordinates)
            {
                var pixelX = sourceTile.X * this.rasterProperties.TileWidth;
                var pixelY = sourceTile.Y * this.rasterProperties.TileHeight;

                if ((pixelX >= this.rasterProperties.ImageWidth) || (pixelY >= this.rasterProperties.ImageHeight))
                {
                    continue;
                }

                var imageBuffer = ReadTiffTile(
                    this.configuration.Location,
                    this.rasterProperties.TileWidth,
                    this.rasterProperties.TileHeight,
                    this.rasterProperties.TileSize,
                    pixelX,
                    pixelY);

                const int PixelDataSize = 4;
                var       stride        = this.rasterProperties.TileWidth * PixelDataSize;
                var       handle        = GCHandle.Alloc(imageBuffer, GCHandleType.Pinned);

                try
                {
                    var offsetX = (sourceTile.X - tileMinX) * sourceTileWidth;
                    var offsetY = (sourceTile.Y - tileMinY) * sourceTileHeight;

                    var sourceImageInfo = new SKImageInfo(
                        width: this.rasterProperties.TileWidth,
                        height: this.rasterProperties.TileHeight,
                        colorType: SKColorType.Rgba8888,
                        alphaType: SKAlphaType.Premul);

                    using var sourceImage = SKImage.FromPixels(sourceImageInfo, handle.AddrOfPinnedObject());

                    canvas.DrawImage(sourceImage, SKRect.Create(offsetX, offsetY, sourceImage.Width, sourceImage.Height));

                    // For debug
                    ////using var borderPen = new SKPaint { Color = SKColors.Magenta, StrokeWidth = 5.0f, IsStroke = true, };
                    ////canvas.DrawRect(new SKRect(offsetX, offsetY, offsetX + sourceImage.Width, offsetY + sourceImage.Height), borderPen);
                    ////canvas.DrawText($"R = {sourceTile.Y * this.rasterProperties.TileHeight}", offsetX, offsetY, new SKFont(SKTypeface.FromFamilyName("Arial"), 36.0f), new SKPaint { Color = SKColors.Magenta });
                }
                finally
                {
                    handle.Free();
                }
            }

            // TODO: ! better image transformation / reprojection between coordinate systems

            // Clip and scale to requested size of output image
            var pixelOffsetX = XToGeoTiffPixelX(this.rasterProperties, tileBounds.Left) - sourceTileWidth * tileMinX;
            var pixelOffsetY = YToGeoTiffPixelY(this.rasterProperties, tileBounds.Top) - sourceTileHeight * tileMinY;
            var pixelWidth   = XToGeoTiffPixelX(this.rasterProperties, tileBounds.Right) - XToGeoTiffPixelX(this.rasterProperties, tileBounds.Left);
            var pixelHeight  = YToGeoTiffPixelY(this.rasterProperties, tileBounds.Bottom) - YToGeoTiffPixelY(this.rasterProperties, tileBounds.Top);
            //var sourceRectangle = SKRect.Create((int)Math.Round(pixelOffsetX), (int)Math.Round(pixelOffsetY), (int)Math.Round(pixelWidth), (int)Math.Round(pixelHeight));
            var sourceRectangle = SKRect.Create((float)pixelOffsetX, (float)pixelOffsetY, (float)pixelWidth, (float)pixelHeight);
            var destRectangle   = SKRect.Create(0, 0, width, height);

            using SKImage canvasImage = surface.Snapshot();
            outputCanvas.DrawImage(canvasImage, sourceRectangle, destRectangle, new SKPaint {
                FilterQuality = SKFilterQuality.High,
            });
        }
示例#3
0
        private static M.RasterProperties ReadGeoTiffProperties(string path)
        {
            using var tiff = Tiff.Open(path, ModeOpenReadTiff);

            var planarConfig = (PlanarConfig)tiff.GetField(TiffTag.PLANARCONFIG)[0].ToInt();

            if (planarConfig != PlanarConfig.CONTIG)
            {
                throw new FormatException($"Only single image plane storage organization ({PlanarConfig.CONTIG}) is supported");
            }

            if (!tiff.IsTiled())
            {
                throw new FormatException($"Only tiled storage scheme is supported");
            }

            var imageWidth  = tiff.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
            var imageHeight = tiff.GetField(TiffTag.IMAGELENGTH)[0].ToInt();

            var tileWidth  = tiff.GetField(TiffTag.TILEWIDTH)[0].ToInt();
            var tileHeight = tiff.GetField(TiffTag.TILELENGTH)[0].ToInt();

            // ModelPixelScale  https://freeimage.sourceforge.io/fnet/html/CC586183.htm
            var modelPixelScale = tiff.GetField(TiffTag.GEOTIFF_MODELPIXELSCALETAG);
            var pixelSizesCount = modelPixelScale[0].ToInt();
            var pixelSizes      = modelPixelScale[1].ToDoubleArray();

            // ModelTiePoints  https://freeimage.sourceforge.io/fnet/html/38F9430A.htm
            var tiePointTag    = tiff.GetField(TiffTag.GEOTIFF_MODELTIEPOINTTAG);
            var tiePointsCount = tiePointTag[0].ToInt();
            var tiePoints      = tiePointTag[1].ToDoubleArray();

            if ((tiePoints.Length != 6) || (tiePoints[0] != 0) || (tiePoints[1] != 0) || (tiePoints[2] != 0) || (tiePoints[5] != 0))
            {
                throw new FormatException($"Only single tie point is supported"); // TODO: Only simple tie points scheme is supported
            }

            var modelTransformation = tiff.GetField(TiffTag.GEOTIFF_MODELTRANSFORMATIONTAG);

            if (modelTransformation != null)
            {
                throw new FormatException($"Only simple projection without transformation is supported");
            }

            var srId = 0;

            // Simple check SRS of GeoTIFF tie points
            var geoKeys = tiff.GetField(TiffTag.GEOTIFF_GEOKEYDIRECTORYTAG);

            if (geoKeys != null)
            {
                var geoDoubleParams = tiff.GetField(TiffTag.GEOTIFF_GEODOUBLEPARAMSTAG);
                double[]? doubleParams = null;
                if (geoDoubleParams != null)
                {
                    doubleParams = geoDoubleParams[1].ToDoubleArray();
                }

                var geoAsciiParams = tiff.GetField(TiffTag.GEOTIFF_GEOASCIIPARAMSTAG);

                // Array of GeoTIFF GeoKeys values
                var keys = geoKeys[1].ToUShortArray();
                if (keys.Length > 4)
                {
                    // Header={KeyDirectoryVersion, KeyRevision, MinorRevision, NumberOfKeys}
                    var keyDirectoryVersion = keys[0];
                    var keyRevision         = keys[1];
                    var minorRevision       = keys[2];
                    var numberOfKeys        = keys[3];
                    for (var keyIndex = 4; keyIndex < keys.Length;)
                    {
                        switch (keys[keyIndex])
                        {
                        case (ushort)GeoTiff.Key.GTModelTypeGeoKey:
                        {
                            var modelType = (GeoTiff.ModelType)keys[keyIndex + 3];
                            if (!((modelType == GeoTiff.ModelType.Projected) || (modelType == GeoTiff.ModelType.Geographic)))
                            {
                                throw new FormatException("Only coordinate systems ModelTypeProjected (1) or ModelTypeGeographic (2) are supported");
                            }

                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.GTRasterTypeGeoKey:
                        {
                            var rasterType = (GeoTiff.RasterType)keys[keyIndex + 3];         // TODO: use RasterTypeCode value
                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.GTCitationGeoKey:
                        {
                            var gtc = keys[keyIndex + 3];
                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.GeographicTypeGeoKey:
                        {
                            var geographicType = keys[keyIndex + 3];
                            if (geographicType != 4326)
                            {
                                throw new FormatException("Only EPSG:4326 geodetic coordinate system is supported");
                            }

                            srId      = geographicType;
                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.GeogCitationGeoKey:
                        {
                            var geogCitation = keys[keyIndex + 3];
                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.GeogGeodeticDatumGeoKey:
                        {
                            // 6.3.2.2 Geodetic Datum Codes
                            var geodeticDatum = keys[keyIndex + 3];
                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.GeogPrimeMeridianGeoKey:
                        {
                            // 6.3.2.4 Prime Meridian Codes
                            var primeMeridian = keys[keyIndex + 3];
                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.GeogAngularUnitsGeoKey:
                        {
                            var geogAngularUnit = (GeoTiff.AngularUnits)keys[keyIndex + 3];
                            if (geogAngularUnit != GeoTiff.AngularUnits.Degree)
                            {
                                throw new FormatException("Only degree angular unit is supported");
                            }

                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.GeogAngularUnitSizeGeoKey:
                        {
                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.GeogEllipsoidGeoKey:
                        {
                            // 6.3.2.3 Ellipsoid Codes
                            var geogEllipsoid = keys[keyIndex + 3];
                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.GeogSemiMajorAxisGeoKey:
                        {
                            if (doubleParams == null)
                            {
                                throw new FormatException($"Double values were not found in '{TiffTag.GEOTIFF_GEODOUBLEPARAMSTAG}' tag");
                            }

                            var geogSemiMajorAxis = doubleParams[keys[keyIndex + 3]];
                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.GeogSemiMinorAxisGeoKey:
                        {
                            if (doubleParams == null)
                            {
                                throw new FormatException($"Double values were not found in '{TiffTag.GEOTIFF_GEODOUBLEPARAMSTAG}' tag");
                            }

                            var geogSemiMinorAxis = doubleParams[keys[keyIndex + 3]];
                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.GeogInvFlatteningGeoKey:
                        {
                            if (doubleParams == null)
                            {
                                throw new FormatException($"Double values were not found in '{TiffTag.GEOTIFF_GEODOUBLEPARAMSTAG}' tag");
                            }

                            var geogInvFlattening = doubleParams[keys[keyIndex + 3]];
                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.GeogAzimuthUnitsGeoKey:
                        {
                            // 6.3.1.4 Angular Units Codes
                            var geogAzimuthUnits = (GeoTiff.AngularUnits)keys[keyIndex + 3];
                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.GeogPrimeMeridianLongGeoKey:
                        {
                            var geogPrimeMeridianLong = keys[keyIndex + 3];
                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.ProjectedCSTypeGeoKey:
                        {
                            var projectedCSType = keys[keyIndex + 3];
                            if (projectedCSType != 3857)
                            {
                                throw new FormatException($"Only EPSG:3857 projected coordinate system is supported (input was: {projectedCSType})");
                            }

                            // TODO: UTM (EPSG:32636 and others) support
                            srId      = projectedCSType;
                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.PCSCitationGeoKey:
                        {
                            keyIndex += 4;
                            break;
                        }

                        case (ushort)GeoTiff.Key.ProjLinearUnitsGeoKey:
                        {
                            var linearUnit = (GeoTiff.LinearUnits)keys[keyIndex + 3];
                            if (linearUnit != GeoTiff.LinearUnits.Meter)
                            {
                                throw new FormatException("Only meter linear unit is supported");
                            }

                            keyIndex += 4;
                            break;
                        }

                        default:
                        {
                            keyIndex += 4;         // Just skipping all unprocessed keys
                            break;
                        }
                        }
                    }
                }
            }

            M.GeographicalBounds?geographicalBounds = null;
            M.Bounds?            projectedBounds = null;
            double pixelWidth = 0.0, pixelHeight = 0.0;

            switch (srId)
            {
            case 4326:     // TODO: const
                {
                    geographicalBounds = new M.GeographicalBounds(
                        tiePoints[3],
                        tiePoints[4] - imageHeight * pixelSizes[1],
                        tiePoints[3] + imageWidth * pixelSizes[0],
                        tiePoints[4]);

                    projectedBounds = new M.Bounds(
                        U.WebMercator.X(tiePoints[3]),
                        U.WebMercator.Y(tiePoints[4] - imageHeight * pixelSizes[1]),
                        U.WebMercator.X(tiePoints[3] + imageWidth * pixelSizes[0]),
                        U.WebMercator.Y(tiePoints[4]));

                    pixelWidth  = U.WebMercator.X(tiePoints[3] + pixelSizes[0]) - U.WebMercator.X(tiePoints[3]);
                    pixelHeight = U.WebMercator.Y(tiePoints[4]) - U.WebMercator.Y(tiePoints[4] - pixelSizes[1]);

                    break;
                }

            case 3857:     // TODO: const
            {
                projectedBounds = new M.Bounds(
                    tiePoints[3],
                    tiePoints[4] - imageHeight * pixelSizes[1],
                    tiePoints[3] + imageWidth * pixelSizes[0],
                    tiePoints[4]);

                geographicalBounds = new M.GeographicalBounds(
                    U.WebMercator.Longitude(tiePoints[3]),
                    U.WebMercator.Latitude(tiePoints[4] - imageHeight * pixelSizes[1]),
                    U.WebMercator.Longitude(tiePoints[3] + imageWidth * pixelSizes[0]),
                    U.WebMercator.Latitude(tiePoints[4]));

                pixelWidth  = pixelSizes[0];
                pixelHeight = pixelSizes[1];

                break;
            }

            default:
            {
                throw new InvalidOperationException($"SRID '{srId}' is not supported");
            }
            }

            var result = new M.RasterProperties
            {
                Srid               = srId,
                ImageWidth         = imageWidth,
                ImageHeight        = imageHeight,
                TileWidth          = tileWidth,
                TileHeight         = tileHeight,
                TileSize           = tiff.TileSize(),
                ProjectedBounds    = projectedBounds,
                GeographicalBounds = geographicalBounds,
                PixelWidth         = pixelWidth,
                PixelHeight        = pixelHeight,
            };

            return(result);
        }