public void CanConvertLatLngToPixel(double latDegrees, double lngDegrees, float xExpected, float yExpected) { var pixelPoint = TileServiceUtils.LatLngToPixel(latDegrees.LatDegreesToRadians(), lngDegrees.LonDegreesToRadians(), 32768); Assert.Equal(xExpected, pixelPoint.x, 0); Assert.Equal(yExpected, pixelPoint.y, 0); }
private void TryZoomIn(MapParameters parameters, out int requiredWidth, out int requiredHeight, out Point pixelMin, out Point pixelMax) { pixelMin = TileServiceUtils.LatLngToPixel(parameters.bbox.minLat, parameters.bbox.minLng, parameters.numTiles); pixelMax = TileServiceUtils.LatLngToPixel(parameters.bbox.maxLat, parameters.bbox.maxLng, parameters.numTiles); requiredWidth = (int)Math.Abs(pixelMax.x - pixelMin.x); requiredHeight = (int)Math.Abs(pixelMax.y - pixelMin.y); //See if we can zoom in - occurs when the requested tile size is much larger than the bbox var zoomedWidth = requiredWidth; var zoomedHeight = requiredHeight; int zoomLevel = parameters.zoomLevel; Point zoomedPixelMin = pixelMin; Point zoomedPixelMax = pixelMax; long numTiles = parameters.numTiles; //allow a 15% margin extra otherwise if the tile is only a few pixels bigger than the calculated zoom //we use the smaller zoom level and end up with lots of space around the data. //AdjustBoundingBoxToFit handles the bigger size. var mapWidth = parameters.mapWidth * 1.15; var mapHeight = parameters.mapHeight * 1.15; while (zoomedWidth < mapWidth && zoomedHeight < mapHeight && zoomLevel < MAX_ZOOM_LEVEL) { parameters.zoomLevel = zoomLevel; parameters.numTiles = numTiles; requiredWidth = zoomedWidth; requiredHeight = zoomedHeight; pixelMin = zoomedPixelMin; pixelMax = zoomedPixelMax; zoomLevel++; numTiles = TileServiceUtils.NumberOfTiles(zoomLevel); zoomedPixelMin = TileServiceUtils.LatLngToPixel(parameters.bbox.minLat, parameters.bbox.minLng, numTiles); zoomedPixelMax = TileServiceUtils.LatLngToPixel(parameters.bbox.maxLat, parameters.bbox.maxLng, numTiles); zoomedWidth = (int)Math.Abs(zoomedPixelMax.x - zoomedPixelMin.x); zoomedHeight = (int)Math.Abs(zoomedPixelMax.y - zoomedPixelMin.y); } }
/// <summary> /// Get the map parameters for the report tile /// </summary> public MapParameters GetMapParameters(string bbox, int width, int height, bool addMargin, bool adjustBoundingBox) { log.LogDebug($"GetMapParameters: bbox={bbox}, width={width}, height={height}, addMargin={addMargin}, adjustBoundingBox={adjustBoundingBox}"); var bboxRadians = boundingBoxHelper.GetBoundingBox(bbox); MapBoundingBox mapBox = new MapBoundingBox { minLat = bboxRadians.BottomLeftLat, minLng = bboxRadians.BottomLeftLon, maxLat = bboxRadians.TopRightLat, maxLng = bboxRadians.TopRightLon }; int zoomLevel = TileServiceUtils.CalculateZoomLevel(mapBox.maxLat - mapBox.minLat, mapBox.maxLng - mapBox.minLng); long numTiles = TileServiceUtils.NumberOfTiles(zoomLevel); MapParameters parameters = new MapParameters { bbox = mapBox, zoomLevel = zoomLevel, numTiles = numTiles, mapWidth = width, mapHeight = height, addMargin = addMargin }; if (adjustBoundingBox) { boundingBoxService.AdjustBoundingBoxToFit(parameters); } parameters.pixelTopLeft = TileServiceUtils.LatLngToPixel(mapBox.maxLat, mapBox.minLng, parameters.numTiles); log.LogDebug("MapParameters: " + JsonConvert.SerializeObject(parameters)); return(parameters); }
/// <summary> /// Joins standard size DXF tiles together to form one large tile for the report /// </summary> private async Task <byte[]> JoinDxfTiles(MapParameters parameters, FileData dxfFile) { log.LogDebug($"JoinDxfTiles: {dxfFile.ImportedFileUid}, {dxfFile.Name}"); //Find the tiles that the bounding box fits into. MasterDataModels.Point tileTopLeft = MasterDataModels.WebMercatorProjection.PixelToTile(parameters.pixelTopLeft); MasterDataModels.Point pixelBottomRight = TileServiceUtils.LatLngToPixel( parameters.bbox.minLat, parameters.bbox.maxLng, parameters.numTiles); MasterDataModels.Point tileBottomRight = MasterDataModels.WebMercatorProjection.PixelToTile(pixelBottomRight); int xnumTiles = (int)(tileBottomRight.x - tileTopLeft.x) + 1; int ynumTiles = (int)(tileBottomRight.y - tileTopLeft.y) + 1; int width = xnumTiles * MasterDataModels.WebMercatorProjection.TILE_SIZE; int height = ynumTiles * MasterDataModels.WebMercatorProjection.TILE_SIZE; using (Image <Rgba32> tileBitmap = new Image <Rgba32>(width, height)) { //Find the offset of the bounding box top left point inside the top left tile var point = new MasterDataModels.Point { x = tileTopLeft.x * MasterDataModels.WebMercatorProjection.TILE_SIZE, y = tileTopLeft.y * MasterDataModels.WebMercatorProjection.TILE_SIZE }; //Clip to the actual bounding box within the tiles. int clipWidth = parameters.mapWidth; int clipHeight = parameters.mapHeight; int xClipTopLeft = (int)(parameters.pixelTopLeft.x - point.x); int yClipTopLeft = (int)(parameters.pixelTopLeft.y - point.y); //Unlike System.Drawing, which allows the clipRect to have negative x, y and which moves as well as clips when used with DrawImage //as the source rectangle, ImageSharp does not respect negative values. So we will have to do extra work in this situation. if (xClipTopLeft < 0) { clipWidth += xClipTopLeft; xClipTopLeft = 0; } if (yClipTopLeft < 0) { clipHeight += yClipTopLeft; yClipTopLeft = 0; } var clipRect = new Rectangle(xClipTopLeft, yClipTopLeft, clipWidth, clipHeight); //Join all the DXF tiles into one large tile await JoinDataOceanTiles(dxfFile, tileTopLeft, tileBottomRight, tileBitmap, parameters.zoomLevel); //Now clip the large tile tileBitmap.Mutate(ctx => ctx.Crop(clipRect)); if (clipWidth >= parameters.mapWidth && clipHeight >= parameters.mapHeight) { return(tileBitmap.BitmapToByteArray()); } //and resize it if required tile area was overlapping rather than within the large tile //(negative clip values above) using (Image <Rgba32> resizedBitmap = new Image <Rgba32>(parameters.mapWidth, parameters.mapHeight)) { Point offset = new Point(parameters.mapWidth - clipWidth, parameters.mapHeight - clipHeight); resizedBitmap.Mutate(ctx => ctx.DrawImage(tileBitmap, PixelBlenderMode.Normal, 1f, offset)); return(resizedBitmap.BitmapToByteArray()); } } }