public TileRange ComputeBoundingBoxTileRange(BoundingBox bbox, ImageryProvider provider, int minTilesPerImage = 4) { // TODO good one, to test // texture quality would be expressed in tex size //TileUtils.BestMapView(new double[] { bbox.xMin, bbox.yMin, bbox.xMax, bbox.yMax }, 16384, 16384, 0, provider.TileSize, out double centerLat, out double centerLon, out double zoomBestView); TileRange tiles = new TileRange(provider); BoundingBox mapBbox; Point <double> topLeft; Point <double> bottomRight; // optimal zoom calculation (maybe there's a direct way) // see : https://docs.microsoft.com/fr-fr/azure/azure-maps/zoom-levels-and-tile-grid?tabs=csharp#tile-math-source-code (prepare tissues for nose bleed, and don't go if you're allergic to trigo and/or magical constants) // calculate the size of the full bbox at increasing zoom levels // until the full image would be greater than a tile int zoom = 0; int maxSize = 256 * minTilesPerImage; /* fixed to 256px to limit number of tiles */ do { zoom++; // coords are pixels in global map image (see TileUtils.MapSize(zoom)) topLeft = TileUtils.PositionToGlobalPixel(new LatLong(bbox.yMax, bbox.xMin), zoom, provider.TileSize); bottomRight = TileUtils.PositionToGlobalPixel(new LatLong(bbox.yMin, bbox.xMax), zoom, provider.TileSize); mapBbox = new BoundingBox(topLeft.X, bottomRight.X, topLeft.Y, bottomRight.Y); } while (zoom < provider.MaxZoom && Math.Min(mapBbox.Width, mapBbox.Height) < maxSize); // now we have the minimum zoom without image // we can know which tiles are needed tiles.Start = new MapTileInfo(TileUtils.GlobalPixelToTileXY(topLeft.X, topLeft.Y, provider.TileSize), zoom, provider.TileSize); tiles.End = new MapTileInfo(TileUtils.GlobalPixelToTileXY(bottomRight.X, bottomRight.Y, provider.TileSize), zoom, provider.TileSize); tiles.AreaOfInterest = mapBbox; return(tiles); }
public TextureInfo ConstructTextureWithGpxTrack(TileRange tiles, BoundingBox bbox, string fileName, TextureImageFormat mimeType, IEnumerable <GeoPoint> gpxPoints) { // where is the bbox in the final image ? // get pixel in full map int zoomLevel = tiles.First().TileInfo.Zoom; var projectedBbox = ConvertWorldToMap(bbox, zoomLevel); var tilesBbox = GetTilesBoundingBox(tiles); int xOffset = (int)(tilesBbox.xMin - projectedBbox.xMin); int yOffset = (int)(tilesBbox.yMin - projectedBbox.yMin); //DrawDebugBmpBbox(tiles, localBbox, tilesBbox, fileName, mimeType); int tileSize = tiles.Provider.TileSize; var pointsOnTexture = gpxPoints.Select(pt => TileUtils.LatLongToPixelXY(pt.Latitude, pt.Longitude, zoomLevel)) .Select(pt => new PointF(pt.X - (int)projectedBbox.xMin, pt.Y - (int)projectedBbox.yMin)); #if NETFULL ImageFormat format = mimeType == TextureImageFormat.image_jpeg ? ImageFormat.Jpeg : ImageFormat.Png; using (Bitmap bmp = new Bitmap((int)projectedBbox.Width, (int)projectedBbox.Height)) { using (Graphics g = Graphics.FromImage(bmp)) { foreach (var tile in tiles) { using (MemoryStream stream = new MemoryStream(tile.Image)) { using (Image tileImg = Image.FromStream(stream)) { int x = (tile.TileInfo.X - tiles.Start.X) * tileSize + xOffset; int y = (tile.TileInfo.Y - tiles.Start.Y) * tileSize + yOffset; g.DrawImage(tileImg, x, y); } } } } bmp.Save(fileName, format); } throw new NotImplementedException("GPX drawing not implemented yet in .Net Full"); #else using (Image <Rgba32> outputImage = new Image <Rgba32>((int)projectedBbox.Width, (int)projectedBbox.Height)) { foreach (var tile in tiles) { using (Image <Rgba32> tileImg = Image.Load(tile.Image)) { int x = (tile.TileInfo.X - tiles.Start.X) * tileSize + xOffset; int y = (tile.TileInfo.Y - tiles.Start.Y) * tileSize + yOffset; outputImage.Mutate(o => o .DrawImage(tileImg, new Point(x, y), 1f) ); } } outputImage.Mutate(o => o .DrawLines(new Rgba32(1, 0, 0, 1f), 5f, pointsOnTexture.ToArray()) ); // with encoder //IImageEncoder encoder = ConvertFormat(mimeType); //outputImage.Save(fileName, encoder); outputImage.Save(fileName); } #endif return(new TextureInfo(fileName, mimeType, (int)projectedBbox.Width, (int)projectedBbox.Height, zoomLevel, projectedBbox)); //return new TextureInfo(fileName, format, (int)tilesBbox.Width, (int)tilesBbox.Height); }
public TextureInfo ConstructTexture(TileRange tiles, BoundingBox bbox, string fileName, TextureImageFormat mimeType) { // where is the bbox in the final image ? // get pixel in full map int zoomLevel = tiles.First().TileInfo.Zoom; var projectedBbox = ConvertWorldToMap(bbox, zoomLevel); var tilesBbox = GetTilesBoundingBox(tiles); //DrawDebugBmpBbox(tiles, localBbox, tilesBbox, fileName, mimeType); int tileSize = tiles.Provider.TileSize; #if NETFULL ImageFormat format = mimeType == TextureImageFormat.image_jpeg ? ImageFormat.Jpeg : ImageFormat.Png; using (Bitmap bmp = new Bitmap((int)projectedBbox.Width, (int)projectedBbox.Height)) { int xOffset = (int)(tilesBbox.xMin - projectedBbox.xMin); int yOffset = (int)(tilesBbox.yMin - projectedBbox.yMin); using (Graphics g = Graphics.FromImage(bmp)) { foreach (var tile in tiles) { using (MemoryStream stream = new MemoryStream(tile.Image)) { using (Image tileImg = Image.FromStream(stream)) { int x = (tile.TileInfo.X - tiles.Start.X) * tileSize + xOffset; int y = (tile.TileInfo.Y - tiles.Start.Y) * tileSize + yOffset; g.DrawImage(tileImg, x, y); } } } } bmp.Save(fileName, format); } #else using (Image <Rgba32> outputImage = new Image <Rgba32>((int)projectedBbox.Width, (int)projectedBbox.Height)) { int xOffset = (int)(tilesBbox.xMin - projectedBbox.xMin); int yOffset = (int)(tilesBbox.yMin - projectedBbox.yMin); foreach (var tile in tiles) { using (Image <Rgba32> tileImg = Image.Load(tile.Image)) { int x = (tile.TileInfo.X - tiles.Start.X) * tileSize + xOffset; int y = (tile.TileInfo.Y - tiles.Start.Y) * tileSize + yOffset; outputImage.Mutate(o => o .DrawImage(tileImg, new Point(x, y), 1f) ); } } // with encoder //IImageEncoder encoder = ConvertFormat(mimeType); //outputImage.Save(fileName, encoder); outputImage.Save(fileName); } #endif return(new TextureInfo(fileName, mimeType, (int)projectedBbox.Width, (int)projectedBbox.Height, zoomLevel, projectedBbox)); //return new TextureInfo(fileName, format, (int)tilesBbox.Width, (int)tilesBbox.Height); }
public TileRange DownloadTiles(BoundingBox bbox, ImageryProvider provider, int minTilesPerImage = 4) { TileRange tiles = this.ComputeBoundingBoxTileRange(bbox, provider, minTilesPerImage); return(this.DownloadTiles(tiles, provider)); }
public TextureInfo ConstructTexture(TileRange tiles, BoundingBox bbox, string fileName, TextureImageFormat mimeType, float quality = 0.98f) { // where is the bbox in the final image ? // get pixel in full map int zoomLevel = tiles.Tiles.First().TileInfo.Zoom; var projectedBbox = ConvertWorldToMap(bbox, zoomLevel, tiles.TileSize); var tilesBbox = GetTilesBoundingBox(tiles); //DrawDebugBmpBbox(tiles, localBbox, tilesBbox, fileName, mimeType); int tileSize = tiles.TileSize; using (Image <Rgba32> outputImage = new Image <Rgba32>((int)Math.Ceiling(projectedBbox.Width), (int)Math.Ceiling(projectedBbox.Height))) { int xOffset = (int)(tilesBbox.xMin - projectedBbox.xMin); int yOffset = (int)(tilesBbox.yMin - projectedBbox.yMin); foreach (var tile in tiles.Tiles) { try { int x = (tile.TileInfo.X - tiles.Start.X) * tileSize + xOffset; int y = (tile.TileInfo.Y - tiles.Start.Y) * tileSize + yOffset; if (x >= projectedBbox.Width || y >= projectedBbox.Height) { continue; } if (tile.Image.Length == 0) { _logger.LogWarning($"Tile {tile.Uri} is empty. Skipping."); continue; } using (Image <Rgba32> tileImg = Image.Load(tile.Image)) { outputImage.Mutate(o => o .DrawImage(tileImg, new Point(x, y), 1f) ); } } catch (Exception ex) { _logger.LogError($"Error while generating texture: {ex.Message}"); } } // with encoder //IImageEncoder encoder = ConvertFormat(mimeType); //outputImage.Save(fileName, encoder); // Make image power of two dimensions if (options.PowerOfTwoImages) { int nearestPowerOf2 = ToNextNearestPowerOf2(Math.Max(outputImage.Width, outputImage.Height)); outputImage.Mutate(o => o.Resize(nearestPowerOf2, nearestPowerOf2)); } SaveImage(outputImage, fileName, quality); } return(new TextureInfo(fileName, mimeType, (int)Math.Ceiling(projectedBbox.Width), (int)Math.Ceiling(projectedBbox.Height), zoomLevel, projectedBbox, tiles.Count)); }
public TileRange DownloadTiles(TileRange tiles, ImageryProvider provider) { Stopwatch sw = Stopwatch.StartNew(); int intervalMs = 1000; if (provider is ITileGenerator generator) { // download tiles using (TimeSpanBlock timer = new TimeSpanBlock("Tile generation", _logger)) { // Max download threads defined in provider var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = provider.MaxDegreeOfParallelism }; var range = tiles.TilesInfo.ToList(); _logger?.LogInformation($"Generating {range.Count} tiles with {provider.Name} generator..."); Parallel.ForEach(range, parallelOptions, tile => { var contentbytes = generator.GenerateTile(tile.X, tile.Y, tile.Zoom); tiles.Add(new MapTile(contentbytes, provider.TileSize, null, tile)); } ); } } else { // download tiles Stopwatch swDownload = Stopwatch.StartNew(); _logger?.LogTrace("Starting images download"); // Max download threads defined in provider var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = provider.MaxDegreeOfParallelism }; var range = tiles.TilesInfo.ToList(); int numTilesDownloaded = 0; _logger?.LogInformation($"Downloading {range.Count} tiles..."); try { Parallel.ForEach(range, parallelOptions, (tile, state) => { Uri tileUri = BuildUri(provider, tile.X, tile.Y, tile.Zoom); var contentBytes = cache.GetTile(tileUri, provider, tile); tiles.Add(new MapTile(contentBytes, provider.TileSize, tileUri, tile)); Interlocked.Increment(ref numTilesDownloaded); if (sw.ElapsedMilliseconds > intervalMs) { _logger.LogInformation($"{numTilesDownloaded:N0}/{range.Count:N0} tiles downloaded..."); sw.Restart(); } } ); } catch (AggregateException ex) { throw ex.GetInnerMostException(); } catch (Exception) { throw; } swDownload.Stop(); _logger?.LogInformation($"DownloadImages done in : {swDownload.Elapsed:g}"); } return(tiles); }