BoundingBox ConvertWorldToMap(BoundingBox bbox, int zoomLevel) { var bboxTopLeft = TileUtils.LatLongToPixelXY(bbox.yMax, bbox.xMin, zoomLevel); var bboxBottomRight = TileUtils.LatLongToPixelXY(bbox.yMin, bbox.xMax, zoomLevel); return(new BoundingBox(bboxTopLeft.X, bboxBottomRight.X, bboxTopLeft.Y, bboxBottomRight.Y)); }
public Uri BuildUri(ImageryProvider provider, int x, int y, int zoom) { string[] serverNodes = provider.UrlModel.Servers; string server = string.Empty; if (serverNodes != null && serverNodes.Length > 0) { _serverCycle = (_serverCycle + 1) % serverNodes.Length; server = serverNodes[_serverCycle]; } string url = provider.UrlModel.UrlFormat; url = url.Replace("{s}", server); url = url.Replace("{x}", x.ToString()); url = url.Replace("{y}", y.ToString()); url = url.Replace("{z}", zoom.ToString()); url = url.Replace("{quadkey}", TileUtils.TileXYToQuadKey(x, y, zoom)); if (url.Contains("{t}")) { var token = GetToken(provider); if (String.IsNullOrWhiteSpace(token)) { var message = $"There is no token found for {provider.Name} provider. Make sure a user secrets are set with a {provider.TokenUserSecretsKey} value."; _logger?.LogError(message); throw new Exception(message); } url = url.Replace("{t}", token); } return(new Uri(url, UriKind.Absolute)); }
public TileRange ComputeBoundingBoxTileRange(BoundingBox bbox, ImageryProvider provider, int minTilesPerImage = 4) { TileRange tiles = new TileRange(provider); BoundingBox mapBbox; PointInt topLeft; PointInt bottomRight; // optimal zoom calculation (maybe there's a direct way) // calculate the size of the full bbox at increasing zoom levels // until the full image would be greater than a tile int zoom = 0; do { zoom++; // coords are pixels in global map image (see TileUtils.MapSize(zoom)) topLeft = TileUtils.LatLongToPixelXY(bbox.yMax, bbox.xMin, zoom); bottomRight = TileUtils.LatLongToPixelXY(bbox.yMin, bbox.xMax, zoom); mapBbox = new BoundingBox(topLeft.X, bottomRight.X, topLeft.Y, bottomRight.Y); }while (zoom < provider.MaxZoom && (mapBbox.Width < provider.TileSize * minTilesPerImage && mapBbox.Height < provider.TileSize * minTilesPerImage)); // now we have the minimum zoom without image // we can know which tiles are needed tiles.Start = new MapTileInfo(TileUtils.PixelXYToTileXY(topLeft.X, topLeft.Y), zoom); tiles.End = new MapTileInfo(TileUtils.PixelXYToTileXY(bottomRight.X, bottomRight.Y), zoom); tiles.AreaOfInterest = mapBbox; return(tiles); }
public List <Vector2> ComputeUVMap(HeightMap heightMap, TextureInfo textureInfo) { /********************************** * We need to map texture pixels to heightmap points * Linear mapping does not work because or Mercator projection distortion * Pseudo code : * for each point * project to texture coordinates (pixelXY at same zoom than texture) * get pixel offset from origin * map this offset to (0 -> texWidth, 0 -> textHeight) => (0->1, 0->1) */ List <Vector2> uvs = new List <Vector2>(heightMap.Count); var bbox = textureInfo.ProjectedBounds; foreach (GeoPoint geoPoint in heightMap.Coordinates) { PointInt projPoint = TileUtils.LatLongToPixelXY(geoPoint.Latitude, geoPoint.Longitude, textureInfo.ProjectedZoom); float xOffset = projPoint.X - (float)bbox.xMin; float uvX = MathHelper.Map(1, textureInfo.Width, 0, 1, xOffset, true); float yOffset = projPoint.Y - (float)bbox.yMin; float uvY = MathHelper.Map(1, textureInfo.Height, 0, 1, yOffset, true); uvs.Add(new Vector2(uvX, uvY)); } return(uvs); }
BoundingBox GetTilesBoundingBox(TileRange tiles) { var bboxTopLeft = TileUtils.TileXYToPixelXY(tiles.Min(tile => tile.TileInfo.X), tiles.Min(tile => tile.TileInfo.Y)); var bboxBottomRight = TileUtils.TileXYToPixelXY(tiles.Max(tile => tile.TileInfo.X) + 1, tiles.Max(tile => tile.TileInfo.Y) + 1); return(new BoundingBox(bboxTopLeft.X, bboxBottomRight.X, bboxTopLeft.Y, bboxBottomRight.Y)); }
public TileRange DownloadTiles(BoundingBox bbox, ImageryProvider provider, int minTilesPerImage = 4) { TileRange tiles = new TileRange(provider); BoundingBox mapBbox = null; PointInt topLeft = new PointInt(); PointInt bottomRight = new PointInt(); // optimal zoom calculation (maybe there's a direct way) // calculate the size of the full bbox at increasing zoom levels // until the full image would be greater than a tile int zoom = 0; do { zoom++; // coords are pixels in global map image (see TileUtils.MapSize(zoom)) topLeft = TileUtils.LatLongToPixelXY(bbox.yMax, bbox.xMin, zoom); bottomRight = TileUtils.LatLongToPixelXY(bbox.yMin, bbox.xMax, zoom); mapBbox = new BoundingBox(topLeft.X, bottomRight.X, topLeft.Y, bottomRight.Y); }while (zoom < provider.MaxZoom && (mapBbox.Width < provider.TileSize * minTilesPerImage && mapBbox.Height < provider.TileSize * minTilesPerImage)); // now we have the minimum zoom without image // we can know which tiles are needed tiles.Start = new MapTileInfo(TileUtils.PixelXYToTileXY(topLeft.X, topLeft.Y), zoom); tiles.End = new MapTileInfo(TileUtils.PixelXYToTileXY(bottomRight.X, bottomRight.Y), zoom); tiles.AreaOfInterest = mapBbox; // downdload tiles Stopwatch swDownload = Stopwatch.StartNew(); _logger?.LogTrace("Starting images download"); // 2 max download threads var options = new ParallelOptions() { MaxDegreeOfParallelism = provider.MaxDegreeOfParallelism }; var range = tiles.EnumerateRange().ToList(); _logger.LogInformation($"Downloading {range.Count} tiles..."); Parallel.ForEach(range, options, tileInfo => { Uri tileUri = BuildUri(provider, tileInfo.X, tileInfo.Y, tileInfo.Zoom); _logger?.LogInformation($"Downloading {tileUri}"); var contentbytes = _httpClient.GetByteArrayAsync(tileUri).Result; tiles.Add(new MapTile(contentbytes, provider.TileSize, tileUri, tileInfo)); } ); swDownload.Stop(); _logger?.LogInformation($"DownloadImages done in : {swDownload.Elapsed:g}"); return(tiles); }
BoundingBox ConvertWorldToMap(BoundingBox bbox, int zoomLevel, int tileSize) { var bboxTopLeft = TileUtils.PositionToGlobalPixel(new LatLong(bbox.yMax, bbox.xMin), zoomLevel, tileSize); var bboxBottomRight = TileUtils.PositionToGlobalPixel(new LatLong(bbox.yMin, bbox.xMax), zoomLevel, tileSize); return(new BoundingBox(bboxTopLeft.X, bboxBottomRight.X, bboxTopLeft.Y, bboxBottomRight.Y, bbox.zMin, bbox.zMax)); }
public TextureInfo ConstructTextureWithGpxTrack(TileRange tiles, BoundingBox bbox, string fileName, TextureImageFormat mimeType, IEnumerable <GeoPoint> gpxPoints, bool drawGpxVertices = false, Rgba32 color = default(Rgba32), float lineWidth = 5f) { // 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); int xOffset = (int)(tilesBbox.xMin - projectedBbox.xMin); int yOffset = (int)(tilesBbox.yMin - projectedBbox.yMin); //DrawDebugBmpBbox(tiles, localBbox, tilesBbox, fileName, mimeType); int tileSize = tiles.TileSize; var pointsOnTexture = gpxPoints .Select(pt => TileUtils.PositionToGlobalPixel(new LatLong(pt.Latitude, pt.Longitude), zoomLevel, tiles.TileSize)) .Select(pt => new PointF((float)(pt.X - (int)projectedBbox.xMin), (float)(pt.Y - (int)projectedBbox.yMin))); using (Image <Rgba32> outputImage = new Image <Rgba32>((int)projectedBbox.Width, (int)projectedBbox.Height)) { foreach (var tile in tiles.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(color == default(Rgba32) ? new Rgba32(1, 0, 0, 1f) : color, lineWidth, pointsOnTexture.ToArray()) ); if (drawGpxVertices) { PathCollection pc = new PathCollection(pointsOnTexture.Select(p => new EllipsePolygon(p, new SizeF(10f, 10f)))); outputImage.Mutate(o => o.Draw(GraphicsOptions.Default, Pens.Solid(Rgba32.Violet, 3), pc)); } // with encoder //IImageEncoder encoder = ConvertFormat(mimeType); //outputImage.Save(fileName, encoder); outputImage.Save(fileName); } return(new TextureInfo(fileName, mimeType, (int)projectedBbox.Width, (int)projectedBbox.Height, zoomLevel, projectedBbox)); }
BoundingBox GetTilesBoundingBox(TileRange tiles) { var tileInfos = tiles.TilesInfo.ToList(); var bboxTopLeft = TileUtils.TileXYToGlobalPixel(tileInfos.Min(t => t.X), tileInfos.Min(t => t.Y), tiles.TileSize); var bboxBottomRight = TileUtils.TileXYToGlobalPixel(tileInfos.Max(t => t.X) + 1, tileInfos.Max(t => t.Y) + 1, tiles.TileSize); return(new BoundingBox(bboxTopLeft.X, bboxBottomRight.X, bboxTopLeft.Y, bboxBottomRight.Y)); }
public MapTileInfo ZoomIn(string quadIndex) { if (Zoom == 23) { return(this); } TileUtils.QuadKeyToTileXY(string.Concat(TileUtils.TileXYToQuadKey(X, Y, Zoom), quadIndex), out int x0, out int y0, out int z0); return(new MapTileInfo(x0, y0, z0, this.TileSize)); }
public byte[] GenerateTile(int x, int y, int zoom) { byte[] tileBytes = null; var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); var corner = TileUtils.TileXYToGlobalPixel(x, y, TileSize); var latLong = TileUtils.GlobalPixelToPosition(new Point <double>(corner.X, corner.Y), zoom, TileSize); var latLongOffset = TileUtils.GlobalPixelToPosition(new Point <double>(corner.X + TileSize, corner.Y + TileSize), zoom, TileSize); var graticules = graticuleService.DrawCore(latLong, latLongOffset); using (Image <Rgba32> outputImage = new Image <Rgba32>(this.TileSize, this.TileSize)) { string tileText = $"{x}/{y}/{zoom}{Environment.NewLine}"; outputImage.Mutate(o => o .Fill(Rgba32.White) .DrawText(tileText, font, Rgba32.Black, new PointF(10, 10)) ); outputImage.Mutate(o => DrawGraticules(o, graticules, corner, zoom)); // Test if (DebugPoint != null) { var testPixel = TileUtils.PositionToGlobalPixel(new LatLong(DebugPoint.Latitude, DebugPoint.Longitude), zoom, TileSize); var testTile = TileUtils.GlobalPixelToTileXY(testPixel.X, testPixel.Y, TileSize); // Draw test pixel if (testTile.X == x && testTile.Y == y) { var basex = TileUtils.TileXYToGlobalPixel(x, y, TileSize); var ptLoc = new PointF((float)(testPixel.X - basex.X), (float)(testPixel.Y - basex.Y)); outputImage.Mutate(o => o.DrawLines(Rgba32.Blue, 1f, new PointF[] { new PointF(ptLoc.X - 10, ptLoc.Y - 10), new PointF(ptLoc.X + 10, ptLoc.Y + 10) }) .DrawLines(Rgba32.Blue, 1f, new PointF[] { new PointF(ptLoc.X - 10, ptLoc.Y + 10), new PointF(ptLoc.X + 10, ptLoc.Y - 10) })); } } using (MemoryStream ms = new MemoryStream()) { outputImage.SaveAsPng(ms); tileBytes = ms.ToArray(); } } return(tileBytes); }
public MapTileInfo ZoomOut() { if (Zoom == 1) { return(this); } var quadKey = TileUtils.TileXYToQuadKey(X, Y, Zoom); TileUtils.QuadKeyToTileXY(quadKey.Substring(0, quadKey.Length - 1), out int x0, out int y0, out int z0); return(new MapTileInfo(x0, y0, z0, this.TileSize)); }
public TileRange ComputeBoundingBoxTileRangeForZoomLevel(BoundingBox bbox, ImageryProvider provider, int zoom) { TileRange tiles = new TileRange(provider); Point <double> topLeft = TileUtils.PositionToGlobalPixel(new LatLong(bbox.yMax, bbox.xMin), zoom, provider.TileSize); Point <double> bottomRight = TileUtils.PositionToGlobalPixel(new LatLong(bbox.yMin, bbox.xMax), zoom, provider.TileSize); BoundingBox mapBbox = new BoundingBox(topLeft.X, bottomRight.X, topLeft.Y, bottomRight.Y); 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 TileRange ComputeBoundingBoxTileRangeForTargetResolution(BoundingBox bbox, ImageryProvider imageryProvider, int width, int height) { TileRange tiles = new TileRange(imageryProvider); TileUtils.BestMapView(new double[] { bbox.xMin, bbox.yMin, bbox.xMax, bbox.yMax }, width, height, 0, imageryProvider.TileSize, out double _, out double _, out double zoom); zoom = Math.Round(zoom, 0); var topLeft = TileUtils.PositionToGlobalPixel(new LatLong(bbox.yMax, bbox.xMin), (int)zoom, imageryProvider.TileSize); var bottomRight = TileUtils.PositionToGlobalPixel(new LatLong(bbox.yMin, bbox.xMax), (int)zoom, imageryProvider.TileSize); var mapBbox = new BoundingBox(topLeft.X, bottomRight.X, topLeft.Y, bottomRight.Y); tiles.Start = new MapTileInfo(TileUtils.GlobalPixelToTileXY(topLeft.X, topLeft.Y, imageryProvider.TileSize), (int)zoom, imageryProvider.TileSize); tiles.End = new MapTileInfo(TileUtils.GlobalPixelToTileXY(bottomRight.X, bottomRight.Y, imageryProvider.TileSize), (int)zoom, imageryProvider.TileSize); tiles.AreaOfInterest = mapBbox; return(tiles); }
private void DrawGraticules(IImageProcessingContext img, GraticuleLabels graticules, Point <double> corner, int zoom) { var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 8); foreach (var meridian in graticules.VerticalLabels) { var loc = meridian.worldLocation; var pt = TileUtils.PositionToGlobalPixel(new LatLong(loc.Lat, loc.Long), zoom, TileSize); var xpos = pt.X - corner.X; var start = new PointF((float)xpos, 0); var end = new PointF((float)xpos, TileSize); img.DrawLines(Rgba32.Gray, 1f, new PointF[] { start, end }); try { if (xpos < TileSize - 10) { img.DrawText(Math.Round(loc.Long, 2).ToString(), font, Rgba32.Black, new PointF((float)xpos, 50)); } } catch (Exception) { } } foreach (var parallel in graticules.HorizontalLabels) { var loc = parallel.worldLocation; var pt = TileUtils.PositionToGlobalPixel(new LatLong(loc.Lat, loc.Long), zoom, TileSize); var ypos = pt.Y - corner.Y; var start = new PointF(0, (float)ypos); var end = new PointF(TileSize, (float)ypos); img.DrawLines(Rgba32.Gray, 1f, new PointF[] { start, end }); try { img.DrawText(Math.Round(loc.Lat, 4).ToString(), font, Rgba32.Black, new PointF(50, (float)ypos)); } catch (Exception) { } } }
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); }