public void CanConvertLatLngToPixelOffset() { List <WGSPoint> latLngs = new List <WGSPoint> { new WGSPoint(36.210.LatDegreesToRadians(), -115.025.LonDegreesToRadians()), new WGSPoint(36.205.LatDegreesToRadians(), -115.029.LonDegreesToRadians()), new WGSPoint(36.200.LatDegreesToRadians(), -115.018.LonDegreesToRadians()) }; var topLeft = new Point(100, 250); var pixelPoints = TileServiceUtils.LatLngToPixelOffset(latLngs, topLeft, 32768); var expectedPoints = new PointF[3] { new PointF { X = 1513777.25F, Y = 3287930F }, new PointF { X = 1513684F, Y = 3288074.5F }, new PointF { X = 1513940.38F, Y = 3288218.75F }, }; for (int i = 0; i < 3; i++) { Assert.Equal(expectedPoints[i], pixelPoints[i]); } }
/// <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> /// Gets a map tile with the project boundary drawn on it. /// </summary> /// <param name="parameters">Map parameters such as bounding box, tile size, zoom level etc.</param> /// <param name="project">The project to draw the boundary for</param> /// <returns>A bitmap</returns> public byte[] GetProjectBitmap(MapParameters parameters, ProjectData project) { log.LogInformation($"GetProjectBitmap: project {project.ProjectUID}"); const int PROJECT_BOUNDARY_COLOR = 0x0080FF; //Note: packed is abgr order const int STROKE_TRANSPARENCY = 0x73; //0.45 of FF Rgba32 PROJECT_BOUNDARY_RGBA = new Rgba32((uint)((STROKE_TRANSPARENCY << 24) | PROJECT_BOUNDARY_COLOR)); const int PROJECT_OUTLINE_WIDTH = 4; byte[] projectImage = null; if (project != null) { using (Image <Rgba32> bitmap = new Image <Rgba32>(parameters.mapWidth, parameters.mapHeight)) { var projectPoints = project.ProjectGeofenceWKT.GeometryToPoints(); PointF[] pixelPoints = TileServiceUtils.LatLngToPixelOffset(projectPoints, parameters.pixelTopLeft, parameters.numTiles); bitmap.Mutate(ctx => ctx.DrawPolygon(PROJECT_BOUNDARY_RGBA, PROJECT_OUTLINE_WIDTH, pixelPoints)); projectImage = bitmap.BitmapToByteArray(); } } return(projectImage); }
public void CanCalculateZoomLevel() { Assert.Equal(1, TileServiceUtils.CalculateZoomLevel(Math.PI / 2, Math.PI)); Assert.Equal(11, TileServiceUtils.CalculateZoomLevel(Math.PI / 2000, Math.PI / 1000)); Assert.Equal(21, TileServiceUtils.CalculateZoomLevel(Math.PI / 2000000, Math.PI / 1000000)); Assert.Equal(24, TileServiceUtils.CalculateZoomLevel(Math.PI / 2000000000, Math.PI / 1000000000)); }
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); }
public void RoundingZoomLevelTest() { double diff1 = 0.000365585907798893; double diff2 = 0.000766990393942846; double diff1_1 = 0.000365339467977011; double diff2_1 = 0.000766990393942818; var res1 = TileServiceUtils.CalculateZoomLevel(diff1, diff2); var res2 = TileServiceUtils.CalculateZoomLevel(diff1_1, diff2_1); Assert.Equal(res1, res2); Assert.Equal(13, res1); }
/// <summary> /// Gets a map tile with geofences drawn on it. /// </summary> /// <param name="parameters">Map parameters such as bounding box, tile size, zoom level etc.</param> /// <param name="sites">List of geofences for the customer</param> /// <returns>A bitmap</returns> private byte[] GetGeofencesBitmap(MapParameters parameters, IEnumerable <GeofenceData> sites, bool isSites) { log.LogInformation("GetGeofencesBitmap"); const int DEFAULT_SITE_COLOR = 0x0055FF; byte[] sitesImage = null; if (sites != null && sites.Any()) { // Exclude sites that are too small to be displayed in the current viewport. double viewPortArea = Math.Abs(parameters.bbox.minLatDegrees - parameters.bbox.maxLatDegrees) * Math.Abs(parameters.bbox.minLngDegrees - parameters.bbox.maxLngDegrees); double minArea = viewPortArea / 10000; using (Image <Rgba32> bitmap = new Image <Rgba32>(parameters.mapWidth, parameters.mapHeight)) { foreach (var site in sites) { log.LogDebug($"GetGeofencesBitmap examining site {site.GeofenceUID}"); //Old geofences may not have AreaSqMeters set. if (site.AreaSqMeters > 0 && site.AreaSqMeters < minArea) { log.LogDebug($"GetGeofencesBitmap excluding site {site.GeofenceUID} due to area"); continue; } var sitePoints = site.GeometryWKT.GeometryToPoints().ToList(); //Exclude site if outside bbox bool outside = TileServiceUtils.Outside(parameters.bbox, sitePoints); if (outside) { log.LogDebug($"GetGeofencesBitmap excluding site {site.GeofenceUID} outside bbox"); } else { int siteColor = site.FillColor > 0 ? site.FillColor : (isSites ? DEFAULT_SITE_COLOR : DEFAULT_CUSTOM_BOUNDARY_COLOR); bool transparent = isSites ? site.IsTransparent : true; DrawGeofence(parameters, bitmap, site.GeofenceUID.ToString(), sitePoints, siteColor, transparent); } } sitesImage = bitmap.BitmapToByteArray(); } } return(sitesImage); }
/// <summary> /// Gets a map tile with load/dump locations drawn on it. /// </summary> /// <param name="parameters">Map parameters such as bounding box, tile size, zoom level etc.</param> /// <param name="loadDumpLocations">List of Load/Dump locations</param> /// <returns>A bitmap</returns> public byte[] GetLoadDumpBitmap(MapParameters parameters, List <LoadDumpLocation> loadDumpLocations) { log.LogInformation($"GetLoadDumpBitmap"); //Note: packed is abgr order const uint LOAD_COLOR = 0xFF008F01; //Green 0x018F00 const uint DUMP_COLOR = 0xFFFF3304; //Blue 0x0433FF Rgba32 LOAD_RGBA = new Rgba32(LOAD_COLOR); Rgba32 DUMP_RGBA = new Rgba32(DUMP_COLOR); var loadPen = new Pen <Rgba32>(LOAD_RGBA, 1); var dumpPen = new Pen <Rgba32>(DUMP_RGBA, 1); byte[] loadDumpImage = null; if (loadDumpLocations != null && loadDumpLocations.Any()) { using (Image <Rgba32> bitmap = new Image <Rgba32>(parameters.mapWidth, parameters.mapHeight)) { IEnumerable <WGSPoint> loads = loadDumpLocations .Select(x => new WGSPoint(x.loadLatitude.LatDegreesToRadians(), x.loadLongitude.LonDegreesToRadians())).ToList(); PointF[] pixelPoints = TileServiceUtils.LatLngToPixelOffset(loads, parameters.pixelTopLeft, parameters.numTiles); foreach (var p in pixelPoints) { //Coloring one pixel doesn't show up well therefore do rectangle of 4 pixels var x = (int)p.X; var y = (int)p.Y; var rect = new RectangleF(x, y, 2, 2); bitmap.Mutate(ctx => ctx.Draw(loadPen, rect)); //bitmap[(int) p.X, (int) p.Y] = LOAD_RGBA; } IEnumerable <WGSPoint> dumps = loadDumpLocations .Select(x => new WGSPoint(x.dumpLatitude.LatDegreesToRadians(), x.dumpLongitude.LonDegreesToRadians())).ToList(); pixelPoints = TileServiceUtils.LatLngToPixelOffset(dumps, parameters.pixelTopLeft, parameters.numTiles); foreach (var p in pixelPoints) { var x = (int)p.X; var y = (int)p.Y; var rect = new RectangleF(x, y, 2, 2); bitmap.Mutate(ctx => ctx.Draw(dumpPen, rect)); //bitmap[(int) p.X, (int) p.Y] = DUMP_RGBA; } loadDumpImage = bitmap.BitmapToByteArray(); } } return(loadDumpImage); }
public void PolygonOutsideBoundingBox() { var bbox = new MapBoundingBox { minLat = 36.0, minLng = -115.9, maxLat = 36.5, maxLng = -115.0 }; var points = new List <WGSPoint> { new WGSPoint(35.0, -116.0), new WGSPoint(35.5, -116.0), new WGSPoint(35.5, -116.5), new WGSPoint(35.0, -116.5), new WGSPoint(35.0, -116.0), }; Assert.True(TileServiceUtils.Outside(bbox, points)); }
public void OverlayTilesReturnsTileForEmptyList() { var mapParameters = new MapParameters { mapWidth = 4, mapHeight = 4 }; var result = TileServiceUtils.OverlayTiles(mapParameters, new Dictionary <TileOverlayType, byte[]>()); byte[] expectedResult = null; using (Image <Rgba32> bitmap = new Image <Rgba32>(mapParameters.mapWidth, mapParameters.mapHeight)) { expectedResult = bitmap.BitmapToByteArray(); } for (int i = 0; i < expectedResult.Length; i++) { Assert.Equal(expectedResult[i], result[i]); } }
/// <summary> /// Gets a single tile with various types of data overlayed on it according to what is requested. /// </summary> /// <param name="request">The tile request</param> /// <returns>A TileResult</returns> public async Task <byte[]> GetMapData(TileGenerationRequest request) { log.LogInformation("Getting map tile"); log.LogDebug("TileGenerationRequest: " + JsonConvert.SerializeObject(request)); log.LogDebug("Awaiting tiles to be completed"); var overlays = await Task.WhenAll(request.overlays.Select(overlay => RequestTile(request, overlay))); log.LogDebug($"Tiles completed: {overlays.Count()} overlays"); var overlayTile = TileServiceUtils.OverlayTiles(request.mapParameters, overlays.ToDictionary(k => k.Item1, v => v.Item2)); log.LogDebug("Tiles overlaid"); overlayTile = ScaleTile(request, overlayTile); log.LogDebug("Tiles scaled"); return(overlayTile); }
public void PolygonIntersectsBoundingBox() { var bbox = new MapBoundingBox { minLat = 36.0, minLng = -115.9, maxLat = 36.5, maxLng = -115.0 }; var points = new List <WGSPoint> { new WGSPoint(35.9, -115.5), new WGSPoint(36.3, -115.5), new WGSPoint(36.3, -115.7), new WGSPoint(35.9, -115.7), new WGSPoint(35.9, -115.5), }; Assert.False(TileServiceUtils.Outside(bbox, points)); }
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); } }
private void DrawGeofence(MapParameters parameters, Image <Rgba32> bitmap, string uid, IEnumerable <WGSPoint> points, int color, bool isTransparent) { const byte FILL_TRANSPARENCY = 0x40; //0.25 of FF const byte STROKE_TRANSPARENCY = 0x73; //0.45 of FF const int SITE_OUTLINE_WIDTH = 2; log.LogDebug($"DrawGeofence drawing site or boundary {uid}"); var red = (byte)((color & 0xFF0000) >> 16); var green = (byte)((color & 0x00FF00) >> 8); var blue = (byte)(color & 0x0000FF); PointF[] pixelPoints = TileServiceUtils.LatLngToPixelOffset(points, parameters.pixelTopLeft, parameters.numTiles); if (!isTransparent) { var fillColor = new Rgba32(red, green, blue, FILL_TRANSPARENCY); bitmap.Mutate(ctx => ctx.FillPolygon(fillColor, pixelPoints)); } var lineColor = new Rgba32(red, green, blue, STROKE_TRANSPARENCY); bitmap.Mutate(ctx => ctx.DrawPolygon(lineColor, SITE_OUTLINE_WIDTH, pixelPoints)); }
/// <summary> /// Gets a map tile with alignment center lines drawn on it. /// </summary> /// <param name="parameters">Map parameters such as bounding box, tile size, zoom level etc.</param> /// <param name="projectId">Legacy project ID</param> /// <param name="alignmentPointsList">Points for the project's alignment files</param> /// <returns>A bitmap</returns> public byte[] GetAlignmentsBitmap(MapParameters parameters, long projectId, List <List <WGSPoint> > alignmentPointsList) { log.LogInformation($"GetAlignmentsBitmap: project {projectId}"); byte[] alignmentsImage = null; if (alignmentPointsList != null && alignmentPointsList.Any()) { using (Image <Rgba32> bitmap = new Image <Rgba32>(parameters.mapWidth, parameters.mapHeight)) { foreach (var alignmentPoints in alignmentPointsList) { if (alignmentPoints != null && alignmentPoints.Any()) { PointF[] pixelPoints = TileServiceUtils.LatLngToPixelOffset(alignmentPoints, parameters.pixelTopLeft, parameters.numTiles); bitmap.Mutate(ctx => ctx.DrawLines(Rgba32.Red, 1, pixelPoints)); } } alignmentsImage = bitmap.BitmapToByteArray(); } } return(alignmentsImage); }
/// <summary> /// Gets a map tile with DXF linework on it. /// </summary> /// <param name="parameters">Map parameters such as bounding box, tile size, zoom level etc.</param> /// <param name="dxfFiles">The list of DXF files to overlay tiles for</param> /// <returns>A bitmap</returns> public async Task <byte[]> GetDxfBitmap(MapParameters parameters, IEnumerable <FileData> dxfFiles) { log.LogInformation("GetDxfBitmap"); byte[] overlayData = null; if (dxfFiles != null && dxfFiles.Any()) { List <byte[]> tileList = new List <byte[]>(); foreach (var dxfFile in dxfFiles) { if (dxfFile.ImportedFileType == ImportedFileType.Linework) { tileList.Add(await JoinDxfTiles(parameters, dxfFile)); } } log.LogDebug("Overlaying DXF bitmaps"); overlayData = TileServiceUtils.OverlayTiles(parameters, tileList); } return(overlayData); }
/// <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); }
protected override async Task <ContractExecutionResult> ProcessAsyncEx <T>(T item) { List <FileData> files = null; int zoomLevel = 0; Point topLeftTile = null; int numTiles = 0; BoundingBox2DLatLon bbox = null; if (item is DxfTileRequest request) { files = request.files?.ToList(); bbox = request.bbox; //Calculate zoom level zoomLevel = TileServiceUtils.CalculateZoomLevel(request.bbox.TopRightLat - request.bbox.BottomLeftLat, request.bbox.TopRightLon - request.bbox.BottomLeftLon); log.LogDebug("DxfTileExecutor: BBOX differences {0} {1} {2}", request.bbox.TopRightLat - request.bbox.BottomLeftLat, request.bbox.TopRightLon - request.bbox.BottomLeftLon, zoomLevel); numTiles = TileServiceUtils.NumberOfTiles(zoomLevel); Point topLeftLatLng = new Point(request.bbox.TopRightLat.LatRadiansToDegrees(), request.bbox.BottomLeftLon.LonRadiansToDegrees()); topLeftTile = WebMercatorProjection.LatLngToTile(topLeftLatLng, numTiles); log.LogDebug($"DxfTileExecutor: zoomLevel={zoomLevel}, numTiles={numTiles}, xtile={topLeftTile.x}, ytile={topLeftTile.y}"); } else if (item is DxfTile3dRequest request3d) { files = request3d.files?.ToList(); zoomLevel = request3d.zoomLevel; numTiles = TileServiceUtils.NumberOfTiles(zoomLevel); topLeftTile = new Point { x = request3d.xTile, y = request3d.yTile }; } else { ThrowRequestTypeCastException <DxfTileRequest>(); } log.LogDebug($"DxfTileExecutor: {files?.Count ?? 0} files"); //Short circuit overlaying if there no files to overlay as ForAll is an expensive operation if (files == null || !files.Any()) { byte[] emptyOverlayData; using (var bitmap = new Image <Rgba32>(WebMercatorProjection.TILE_SIZE, WebMercatorProjection.TILE_SIZE)) { emptyOverlayData = bitmap.BitmapToByteArray(); } return(new TileResult(emptyOverlayData)); } log.LogDebug(string.Join(",", files.Select(f => f.Name).ToList())); var tileList = new List <byte[]>(); const string DATA_OCEAN_ROOT_FOLDER_ID_KEY = "DATA_OCEAN_ROOT_FOLDER_ID"; var dataOceanRootFolder = configStore.GetValueString(DATA_OCEAN_ROOT_FOLDER_ID_KEY); if (string.IsNullOrEmpty(dataOceanRootFolder)) { throw new ArgumentException($"Missing environment variable {DATA_OCEAN_ROOT_FOLDER_ID_KEY}"); } //For GeoTIFF files, use the latest version of a file var geoTiffFiles = files.Where(x => x.ImportedFileType == ImportedFileType.GeoTiff).ToList(); if (geoTiffFiles.Any()) { //Find any with multiple versions and remove old ones from the list var latestFiles = geoTiffFiles.GroupBy(g => g.Name).Select(g => g.OrderBy(o => o.SurveyedUtc).Last()).ToList(); foreach (var geoTiffFile in geoTiffFiles) { if (!latestFiles.Contains(geoTiffFile)) { files.Remove(geoTiffFile); } } } var fileTasks = files.Select(async file => { //foreach (var file in request.files) //Check file type to see if it has tiles if (file.ImportedFileType == ImportedFileType.Linework || file.ImportedFileType == ImportedFileType.GeoTiff) { var fullPath = DataOceanFileUtil.DataOceanPath(dataOceanRootFolder, file.CustomerUid, file.ProjectUid); var fileName = DataOceanFileUtil.DataOceanFileName(file.Name, file.ImportedFileType == ImportedFileType.SurveyedSurface || file.ImportedFileType == ImportedFileType.GeoTiff, Guid.Parse(file.ImportedFileUid), file.SurveyedUtc); fileName = DataOceanFileUtil.GeneratedFileName(fileName, file.ImportedFileType); if (zoomLevel >= file.MinZoomLevel) { byte[] tileData = null; if (zoomLevel <= file.MaxZoomLevel || file.MaxZoomLevel == 0) //0 means not calculated { tileData = await GetTileAtRequestedZoom(topLeftTile, zoomLevel, fullPath, fileName); } else if (zoomLevel - file.MaxZoomLevel <= 5) //Don't try to scale if the difference is too excessive { tileData = await GetTileAtHigherZoom(topLeftTile, zoomLevel, fullPath, fileName, file.MaxZoomLevel, numTiles); } else { log.LogDebug( "DxfTileExecutor: difference between requested and maximum zooms too large; not even going to try to scale tile"); } if (tileData != null && tileData.Length > 0) { tileList.Add(tileData); } } } }); await Task.WhenAll(fileTasks); log.LogDebug($"DxfTileExecutor: Overlaying {tileList.Count} tiles"); byte[] overlayData = TileOverlay.OverlayTiles(tileList); return(new TileResult(overlayData)); }
/// <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()); } } }
protected override async Task <ContractExecutionResult> ProcessAsyncEx <T>(T item) { var request = item as DxfTileRequest; if (request == null) { ThrowRequestTypeCastException <DxfTileRequest>(); } string filespaceId = FileDescriptorExtensions.GetFileSpaceId(configStore, log); //Calculate zoom level int zoomLevel = TileServiceUtils.CalculateZoomLevel(request.bbox.TopRightLat - request.bbox.BottomLeftLat, request.bbox.TopRightLon - request.bbox.BottomLeftLon); log.LogDebug("DxfTileExecutor: BBOX differences {0} {1} {2}", request.bbox.TopRightLat - request.bbox.BottomLeftLat, request.bbox.TopRightLon - request.bbox.BottomLeftLon, zoomLevel); int numTiles = TileServiceUtils.NumberOfTiles(zoomLevel); Point topLeftLatLng = new Point(request.bbox.TopRightLat.LatRadiansToDegrees(), request.bbox.BottomLeftLon.LonRadiansToDegrees()); Point topLeftTile = WebMercatorProjection.LatLngToTile(topLeftLatLng, numTiles); log.LogDebug("DxfTileExecutor: zoomLevel={0}, numTiles={1}, xtile={2}, ytile={3}", zoomLevel, numTiles, topLeftTile.x, topLeftTile.y); log.LogDebug("DxfTileExecutor: {0} files", request.files.Count()); //Short circuit overlaying if there no files to overlay as ForAll is an expensive operation if (!request.files.Any()) { byte[] emptyOverlayData = null; using (Bitmap bitmap = new Bitmap(WebMercatorProjection.TILE_SIZE, WebMercatorProjection.TILE_SIZE)) { emptyOverlayData = bitmap.BitmapToByteArray(); } return(new TileResult(emptyOverlayData)); } log.LogDebug(string.Join(",", request.files.Select(f => f.Name).ToList())); List <byte[]> tileList = new List <byte[]>(); var fileTasks = request.files.Select(async file => { //foreach (var file in request.files) //Check file type to see if it has tiles if (file.ImportedFileType == ImportedFileType.Alignment || file.ImportedFileType == ImportedFileType.DesignSurface || file.ImportedFileType == ImportedFileType.Linework) { if (zoomLevel >= file.MinZoomLevel) { var suffix = FileUtils.GeneratedFileSuffix(file.ImportedFileType); string generatedName = FileUtils.GeneratedFileName(file.Name, suffix, FileUtils.DXF_FILE_EXTENSION); byte[] tileData = null; if (zoomLevel <= file.MaxZoomLevel || file.MaxZoomLevel == 0) //0 means not calculated { tileData = await GetTileAtRequestedZoom(topLeftTile, zoomLevel, file.Path, generatedName, filespaceId); } else if (zoomLevel - file.MaxZoomLevel <= 5) //Don't try to scale if the difference is too excessive { tileData = await GetTileAtHigherZoom(topLeftTile, zoomLevel, file.Path, generatedName, filespaceId, file.MaxZoomLevel, numTiles); } else { log.LogDebug( "DxfTileExecutor: difference between requested and maximum zooms too large; not even going to try to scale tile"); } if (tileData != null) { tileList.Add(tileData); } } } }); await Task.WhenAll(fileTasks); //Overlay the tiles. Return an empty tile if none to overlay. log.LogDebug("DxfTileExecutor: Overlaying {0} tiles", tileList.Count); byte[] overlayData = null; System.Drawing.Point origin = new System.Drawing.Point(0, 0); using (Bitmap bitmap = new Bitmap(WebMercatorProjection.TILE_SIZE, WebMercatorProjection.TILE_SIZE)) using (Graphics g = Graphics.FromImage(bitmap)) { foreach (byte[] tileData in tileList) { using (var tileStream = new MemoryStream(tileData)) { Image image = Image.FromStream(tileStream); g.DrawImage(image, origin); } } overlayData = bitmap.BitmapToByteArray(); } return(new TileResult(overlayData)); }
public void CanCalculateNumberOfTiles() { Assert.Equal(1024, TileServiceUtils.NumberOfTiles(10)); }