/// <summary> /// Gets a tile at a higher zoom level and scales part of it for the requested tile /// </summary> /// <param name="topLeftTile">The top left tile coordinates</param> /// <param name="zoomLevel">The requested zoom level</param> /// <param name="path">The file path</param> /// <param name="generatedName">The name of the DXF file (for design and alignment files it is generated)</param> /// <param name="filespaceId">The filespace ID</param> /// <param name="maxZoomLevel">The maximum zoom level for which tiles have been generated</param> /// <param name="numTiles">The number of tiles for the requested zoom level</param> /// <returns>A scaled tile</returns> private async Task <byte[]> GetTileAtHigherZoom(Point topLeftTile, int zoomLevel, string path, string generatedName, string filespaceId, int maxZoomLevel, int numTiles) { int zoomLevelFound = maxZoomLevel; // Calculate the tile coords of the higher zoom level tile that covers the requested tile Point ptRequestedTile = new Point(topLeftTile.y, topLeftTile.x); Point ptRequestedPixel = WebMercatorProjection.TileToPixel(ptRequestedTile); int numTilesAtRequestedZoomLevel = numTiles; Point ptRequestedWorld = WebMercatorProjection.PixelToWorld(ptRequestedPixel, numTilesAtRequestedZoomLevel); int numTilesAtFoundZoomLevel = TileServiceUtils.NumberOfTiles(maxZoomLevel); Point ptHigherPixel = WebMercatorProjection.WorldToPixel(ptRequestedWorld, numTilesAtFoundZoomLevel); Point ptHigherTile = WebMercatorProjection.PixelToTile(ptHigherPixel); //Note PixelToTile uses Math.Floor so this tile coordinate will be the top left of the tile // With the new tile coords of the higher zoom level tile, see if it exists on TCC string fullHigherTileName = $"{FileUtils.ZoomPath(FileUtils.TilePath(path, generatedName), zoomLevelFound)}/{ptHigherTile.y}/{ptHigherTile.x}.png"; log.LogDebug("DxfTileExecutor: looking for higher tile {0}", fullHigherTileName); byte[] tileData = await DownloadTile(filespaceId, fullHigherTileName, "higher"); if (tileData != null) { tileData = ScaleTile(tileData, zoomLevel - zoomLevelFound, ptHigherTile, ptRequestedWorld, numTilesAtFoundZoomLevel); } return(tileData); }
/// <summary> /// Scales a tile /// </summary> /// <param name="tileData">The tile to scale</param> /// <param name="zoomLevelDifference">The difference between the downloaded tile and the rqeuested tile zoom levels</param> /// <param name="ptHigherTile"></param> /// <param name="ptRequestedWorld"></param> /// <param name="numTilesAtFoundZoomLevel">The number of tiles for the higher zoom level</param> /// <returns>A scaled tile</returns> private byte[] ScaleTile(byte[] tileData, int zoomLevelDifference, Point ptHigherTile, Point ptRequestedWorld, int numTilesAtFoundZoomLevel) { // Calculate the tile coords of the BR corner of the higher zoom level tile // so that we can identify which sub-part of it to crop and scale Point ptHigherTileTopLeft = ptHigherTile; Point ptHigherTileBotRight = new Point(ptHigherTile.y + 1, ptHigherTile.x + 1); // Calculate the sub-tile rectangle that we need to crop out of the higher tile // using a simple proportion calculation based on which part of the higher tile // covers the original requested tile in world coordinates Point ptHigherWorldTopLeft = WebMercatorProjection.PixelToWorld(WebMercatorProjection.TileToPixel(ptHigherTileTopLeft), numTilesAtFoundZoomLevel); Point ptHigherWorldBotRight = WebMercatorProjection.PixelToWorld(WebMercatorProjection.TileToPixel(ptHigherTileBotRight), numTilesAtFoundZoomLevel); double ratioX = (ptRequestedWorld.x - ptHigherWorldTopLeft.x) / (ptHigherWorldBotRight.x - ptHigherWorldTopLeft.x); double ratioY = (ptRequestedWorld.y - ptHigherWorldTopLeft.y) / (ptHigherWorldBotRight.y - ptHigherWorldTopLeft.y); int startX = (int)Math.Floor(WebMercatorProjection.TILE_SIZE * ratioX); int startY = (int)Math.Floor(WebMercatorProjection.TILE_SIZE * ratioY); // Calculate how much up-scaling of higher level zoom tile we need to do // based on the difference between the requested and higher zoom levels int croppedTileSize = WebMercatorProjection.TILE_SIZE / (1 << zoomLevelDifference); // Set the crop rectangle and draw it into a new bitmap, scaling it up to the standard tile size Rectangle cropRect = new Rectangle(startX, startY, croppedTileSize, croppedTileSize); log.LogDebug("DxfTileExecutor: crop rectangle x = {0}, y = {1}, size = {2}", startX, startY, croppedTileSize); //source bitmap using (var tileStream = new MemoryStream(tileData)) using (Bitmap higherBitmap = new Bitmap(tileStream)) //destination bitmap using (Bitmap target = new Bitmap(WebMercatorProjection.TILE_SIZE, WebMercatorProjection.TILE_SIZE)) using (Graphics g = Graphics.FromImage(target)) { g.CompositingMode = CompositingMode.SourceCopy; g.CompositingQuality = CompositingQuality.HighQuality; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.SmoothingMode = SmoothingMode.HighQuality; g.PixelOffsetMode = PixelOffsetMode.HighQuality; g.DrawImage(higherBitmap, new Rectangle(0, 0, target.Width, target.Height), cropRect, GraphicsUnit.Pixel); g.Flush(); return(target.BitmapToByteArray()); } }