/// <inheritdoc/> public Stream GetImageStream(int x, int y, int zoom) { // get tile bounds in mercator units var rect = ReprojectionProvider.TileToSphereMercator(x, y, zoom, 6371000); var mapRect = new Tools.Reprojection.MapRectangle(rect.Left, rect.Bottom, rect.Right, rect.Top); // Request the map tile from the ReprojectionService. The returned image stream may be // null, e.g. when the requested tile is out of bounds regarding the inner MapServer configuration. // In that case return a default tile preventing the map from displaying enlarged images from other // zoom levels (> avoids "zoom artifacts"). return((ReprojectionService.GetImageStream(mapRect, new Size(256, 256)) ?? new MemoryStream(defaultTile)).Reset()); }
/// <summary> /// Utility extension that determines the bounding box for a tile matrix set based on MapServiceExtensions.ApproximateBoundingBox. /// </summary> /// <param name="sourceCrs">The CRS of the tile matrix set.</param> /// <param name="targetCrs">The CRS of the bounding box to return.</param> /// <param name="nSupportingPoints">Number of supporting points to use.</param> /// <param name="resizeFactor">An additional factor for resizing the resulting bounding box to be on the safe side.</param> /// <returns>Bounding box.</returns> /// <remarks>Refer to MapServiceExtensions.ApproximateBoundingBox for further documentation.</remarks> public IBoundingBox ApproximateBoundingBox(string sourceCrs, string targetCrs, int nSupportingPoints, double resizeFactor) { // select all corner points var points = this.Select(m => new[] { m.TopLeftCorner, m.BottomRightCorner }).SelectMany(p => p).ToArray(); // create unified map rectangle var mapRect = new Tools.Reprojection.MapRectangle( points.Min(p => p.X), points.Max(p => p.Y), points.Max(p => p.X), points.Min(p => p.Y) ); // approximate bounding box return(mapRect.ApproximateBoundingBox(sourceCrs, targetCrs, nSupportingPoints, resizeFactor)); }
/// <inheritdoc/> public override Stream GetImageStream(IBoundingBox boundingBox, Size requestedSize, out Size effectiveSize) { // must initialize effective size ... assuming we're returning the requested size. effectiveSize = requestedSize; // see ParamsValid for explanation; // if parameters are invalid, we simply return null (= no image available) if (!ParamsValid(boundingBox, requestedSize)) { return(null); } // find the matrix set that is closest to the given bounds and size var matrixSet = SelectTileMatrix(boundingBox, requestedSize); // if no matrix set has been found, we simply return null (= no image available) if (matrixSet == null) { return(null); } // determine the logical width & height of a single tile var logicalTileWidth = (matrixSet.BottomRightCorner.X - matrixSet.TopLeftCorner.X) / matrixSet.MatrixWidth; var logicalTileHeight = (matrixSet.TopLeftCorner.Y - matrixSet.BottomRightCorner.Y) / matrixSet.MatrixHeight; // determine the top left tile that is required to cover the requested bounding box var tileLeft = (int)Math.Floor((boundingBox.MinX - matrixSet.TopLeftCorner.X) / logicalTileWidth); var tileTop = (int)Math.Floor((matrixSet.TopLeftCorner.Y - boundingBox.MaxY) / logicalTileHeight); // determine the lower right tile that is required to cover the requested bounding box var tileRight = (int)Math.Ceiling((boundingBox.MaxX - matrixSet.TopLeftCorner.X) / logicalTileWidth) - 1; var tileBottom = (int)Math.Ceiling((matrixSet.TopLeftCorner.Y - boundingBox.MinY) / logicalTileHeight) - 1; // apply limits as defined by the selected matrix set tileLeft = Math.Max(tileLeft, matrixSet.MinX()); tileTop = Math.Max(tileTop, matrixSet.MinY()); tileRight = Math.Min(tileRight, matrixSet.MaxX()); tileBottom = Math.Min(tileBottom, matrixSet.MaxY()); try { // resulting image Image resultingImage = null; // check if there is anything to be rendered if (tileRight < tileLeft || tileBottom < tileTop) { // there are no tile to be rendered; return empty image resultingImage = CreateBitmap(requestedSize.Width, requestedSize.Height, true); } else { try { // create a temporary image in which we place the tiles var tilesImage = CreateBitmap((tileRight - tileLeft + 1) * matrixSet.TileWidth, (tileBottom - tileTop + 1) * matrixSet.TileHeight); // fail, if image is invalid. This happens e.g. due to an invalid image size. if (tilesImage == null) { return(null); } using (var tileGraphics = Graphics.FromImage(tilesImage)) { // loop through the tiles, request and draw the images // we'll pass on WebExceptions but we will swallow any other exception for (int tx = tileLeft, ox = 0; tx <= tileRight; ++tx, ox += matrixSet.TileWidth) { for (int ty = tileTop, oy = 0; ty <= tileBottom; ++ty, oy += matrixSet.TileHeight) { try { var image = Template.ToLowerInvariant() == "test" ? RenderTestImage(matrixSet.TileWidth, matrixSet.TileHeight, 32, tx, ty) : LoadImage(matrixSet.Identifier, tx, ty); tileGraphics.DrawImageUnscaled(image, new Point(ox, oy)); } // pass on WebException, swallow others catch (WebException) { throw; } } } catch { /* ignored */ } } // determine the logical bounds covered by map rendered above var logicalTileRect = new Tools.Reprojection.MapRectangle( matrixSet.TopLeftCorner.X + tileLeft * logicalTileWidth, matrixSet.TopLeftCorner.Y - tileTop * logicalTileHeight, matrixSet.TopLeftCorner.X + (tileRight + 1) * logicalTileHeight, matrixSet.TopLeftCorner.Y - (tileBottom + 1) * logicalTileHeight ); // test, if the tile image fully covers the bounding box that has initially been requested. // // if that is the case, we'll extract the requested bounding box from that image (unscaled) and // return that. This avoids an additional drawing operation that scales the image and decreases quality. if (boundingBox.MinX >= logicalTileRect.MinX && boundingBox.MaxX <= logicalTileRect.MaxX && boundingBox.MinY >= logicalTileRect.MinX && boundingBox.MaxY <= logicalTileRect.MaxY) { // the tile image fully covers the bounding box that has initially been requested. // determine position and extent of the originally requested bounding box in the tile image var x = (int)Math.Round(tilesImage.Width * (boundingBox.MinX - logicalTileRect.MinX) / logicalTileRect.Size().Width); var y = (int)Math.Round(tilesImage.Height * (logicalTileRect.MaxY - boundingBox.MaxY) / logicalTileRect.Size().Height); var w = (int)Math.Round((double)tilesImage.Width * boundingBox.Size().Width / logicalTileRect.Size().Width); var h = (int)Math.Round((double)tilesImage.Height * boundingBox.Size().Height / logicalTileRect.Size().Height); // the following code "extracts" the originally requested bounding box into the result image // since we're not going to return the requestedSize, we must set effectiveSize accordingly. effectiveSize = new Size(w, h); resultingImage = CreateBitmap(w, h); using (var g = Graphics.FromImage(resultingImage)) g.DrawImageUnscaled(tilesImage, -x, -y); } else { resultingImage = CreateBitmap(requestedSize.Width, requestedSize.Height, true); using (var graphics = Graphics.FromImage(resultingImage)) { // calculate position and size for placing the tile image into // the resulting image that was initially requested var zoomX = boundingBox.Size().Width / requestedSize.Width; var zoomY = boundingBox.Size().Height / requestedSize.Height; var dstRect = new Rectangle( // x & y (int)Math.Round((logicalTileRect.Left - boundingBox.MinX) / zoomX), (int)Math.Round((boundingBox.MaxY - logicalTileRect.Top) / zoomY), // width & height (int)Math.Round((tileRight - tileLeft + 1) * logicalTileWidth / zoomX), (int)Math.Round((tileBottom - tileTop + 1) * logicalTileHeight / zoomY) ); // draw the tile image using a high quality mode graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; graphics.DrawImage(tilesImage, dstRect, 0, 0, tilesImage.Size.Width, tilesImage.Size.Height, GraphicsUnit.Pixel); } } } // pass on WebException, swallow others catch (WebException) { throw; } catch { /* ignored */ } }