/// <summary> /// Creates the transformation function that determines the position of a /// source pixel, given the position of a target pixel. /// </summary> /// <param name="targetMapRectangle">The requested target rectangle.</param> /// <param name="targetSize">The requested target size.</param> /// <param name="sourceBoundingBox">The source bounding box corresponding to the target map rectangle..</param> /// <param name="sourceSize">The source size corresponding to the target size.</param> /// <returns>Position of source pixel</returns> protected virtual Func <PointD, PointD> GetTransformFunction(MapRectangle targetMapRectangle, Size targetSize, IBoundingBox sourceBoundingBox, Size sourceSize) { return(target => { // knowing the map rectangle, we can compute the logical coordinate corresponding to the position of the target pixel var pLogicalTarget = new Location( targetMapRectangle.Left + (targetMapRectangle.Right - targetMapRectangle.Left) * (target.X / (targetSize.Width - 1)), targetMapRectangle.Top - (targetMapRectangle.Top - targetMapRectangle.Bottom) * (target.Y / (targetSize.Height - 1)) ); // transform this logical coordinate into the source CRS var pLogicalSource = TargetToSourceTransformation.Transform(pLogicalTarget); // knowing the source bounding box and its configured orientation, we can now // turn the logical source coordinate into the logical offsets (left and top) var sourceOffset = new SizeD( new[] { ContentAlignment.TopLeft, ContentAlignment.BottomLeft }.Contains(SourceMapService.MinAlignment) ? pLogicalSource.X - sourceBoundingBox.MinX : sourceBoundingBox.MaxX - pLogicalSource.X, new[] { ContentAlignment.BottomLeft, ContentAlignment.BottomRight }.Contains(SourceMapService.MinAlignment) ? sourceBoundingBox.MaxY - pLogicalSource.Y : pLogicalSource.Y - sourceBoundingBox.MinY ); // and finally, we an turn the logical offsets into the pixel position return new PointD( sourceSize.Width * sourceOffset.Width / sourceBoundingBox.Size().Width, sourceSize.Height * sourceOffset.Height / sourceBoundingBox.Size().Height ); }); }
/// <summary> /// Calculates a zoom factor given the logical bounds and the pixel size of a map section. /// </summary> /// <param name="boundingBox">Logical bounds of the map section.</param> /// <param name="pixelSize">Pixel size of the map section.</param> /// <returns>Zoom factor</returns> private static double CalculateZoom(IBoundingBox boundingBox, Size pixelSize) { return(CalculateZoom(boundingBox.Size(), pixelSize)); }
/// <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 */ } }