/// <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());
        }
Beispiel #2
0
        /// <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 */ }
                }