// 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); }
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); }