// TODO: uniform API for build / parse Capabilities XML document

        /// <summary>
        /// Extracts list of Layers from input Capabilities XML document.
        /// </summary>
        /// <param name="xmlDoc">Capabilities XML document.</param>
        /// <returns>List of Layers (flatten, without hierarchy).</returns>
        public static List <M.Layer> GetLayers(XmlDocument xmlDoc)
        {
            var nsManager = new XmlNamespaceManager(xmlDoc.NameTable);

            nsManager.AddNamespace(OwsPrefix, Identifiers.OwsNamespaceUri);
            nsManager.AddNamespace("ns", WmtsNamespaceUri);

            var xpath = "/ns:Capabilities/ns:Contents/ns:Layer";

            var result = new List <M.Layer>();
            var layers = xmlDoc.SelectNodes(xpath, nsManager);

            if (layers != null)
            {
                foreach (XmlNode layer in layers)
                {
                    var layerIdentifier = layer.SelectSingleNode(OwsPrefix + ":" + "Identifier", nsManager);
                    var layerTitle      = layer.SelectSingleNode(OwsPrefix + ":" + "Title", nsManager);

                    M.GeographicalBounds?geographicalBounds = null;
                    var bbox = layer.SelectSingleNode(OwsPrefix + ":" + "WGS84BoundingBox", nsManager);
                    if (bbox != null)
                    {
                        var lowerCorner = bbox.SelectSingleNode(OwsPrefix + ":" + Identifiers.LowerCornerElement, nsManager);
                        var upperCorner = bbox.SelectSingleNode(OwsPrefix + ":" + Identifiers.UpperCornerElement, nsManager);
                        if (lowerCorner != null &&
                            upperCorner != null &&
                            !String.IsNullOrEmpty(lowerCorner.InnerText) &&
                            !String.IsNullOrEmpty(lowerCorner.InnerText))
                        {
                            var lowerCornerValues = lowerCorner.InnerText.Split(' ');
                            var upperCornerValues = upperCorner.InnerText.Split(' ');
                            var minx = Double.Parse(lowerCornerValues[0], CultureInfo.InvariantCulture);
                            var miny = Double.Parse(lowerCornerValues[1], CultureInfo.InvariantCulture);
                            var maxx = Double.Parse(upperCornerValues[0], CultureInfo.InvariantCulture);
                            var maxy = Double.Parse(upperCornerValues[1], CultureInfo.InvariantCulture);
                            geographicalBounds = new M.GeographicalBounds(minx, miny, maxx, maxy);
                        }

                        // TODO: use ResourceURL format="image/jpeg"
                    }

                    result.Add(new M.Layer
                    {
                        Identifier         = layerIdentifier != null ? layerIdentifier.InnerText : String.Empty,
                        Title              = layerTitle != null ? layerTitle.InnerText : String.Empty,
                        GeographicalBounds = geographicalBounds,
                    });
                }
            }

            return(result);
        }
Example #2
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);
        }