13 Answers, 1 is accepted
The following snippet shows how you can export the current map to an image:
private
void
radButton1_Click(
object
sender, EventArgs e)
{
var bitmap =
new
Bitmap((
int
)
this
.radMap1.MapElement.ViewportInPixels.Size.Width, (
int
)
this
.radMap1.MapElement.ViewportInPixels.Height);
Graphics g = Graphics.FromImage(bitmap);
RadGdiGraphics gg =
new
RadGdiGraphics(g);
foreach
(MapVisualElement element
in
this
.radMap1.MapElement.Providers[0].GetContent(
this
.radMap1.MapElement))
{
element.Paint(gg,
this
.radMap1.MapElement);
}
bitmap.Save(@
"D:\test.png"
, ImageFormat.Png);
}
Then you can use RadPdfProcessing to create a pdf document.
I hope this will be useful. Let me know if you have additional questions.
Regards,
Dimitar
Telerik by Progress
Hello,
Is it possible to export the file without using a button click event, like using Radmap in a console app with background WMS tiles?
Thanks
Hello, Yemo,
Note that RadMap uses a MapTileDownloader which internally uses a WebClient calling its DownloadDataAsync method. It seems that in a console application the WebClient.DownloadDataCompleted event is not fired which is used in the MapTileDownloader. That is why the image that is created from the map is blank. The following StackOverflow thread faces a similar case: https://stackoverflow.com/questions/2792608/webclient-downloadstringcompleted-never-fired-in-console-application
The possible solution that I can suggest is to create a custom MapTileDownloader:
static void Main(string[] args)
{
RadMap radMap1 = new RadMap();
radMap1.CreateControl();
string cacheFolder = @"..\..\cache";
OpenStreetMapProvider osmProvider = new OpenStreetMapProvider();
osmProvider.EnableCaching = true;
LocalFileCacheProvider cache = new LocalFileCacheProvider(cacheFolder);
osmProvider.CacheProvider = cache;
osmProvider.TileDownloader = new CustomTileDownLoader();
radMap1.MapElement.Providers.Add(osmProvider);
radMap1.LoadElementTree();
radMap1.Providers[0].Initialize();
osmProvider.ViewportChanged(radMap1.MapElement, ViewportChangeAction.All);
var bitmap = new Bitmap((int)radMap1.MapElement.ViewportInPixels.Size.Width, (int)radMap1.MapElement.ViewportInPixels.Height);
Graphics g = Graphics.FromImage(bitmap);
RadGdiGraphics gg = new RadGdiGraphics(g);
foreach (MapVisualElement element in radMap1.MapElement.Providers[0].GetContent(radMap1.MapElement))
{
element.Paint(gg, radMap1.MapElement);
}
bitmap.Save(@"..\..\test.png", ImageFormat.Png);
Process.Start(@"..\..\test.png");
}
public class CustomTileDownLoader : MapTileDownloader
{
public override void BeginDownloadTile(Uri uri, TileInfo tileInfo)
{
lock (this.webClientsPoolLockObject)
{
if (!this.webClientsPool.ContainsKey(tileInfo.Quadkey))
{
WebClient client = new WebClient();
this.webClientsPool.Add(tileInfo.Quadkey, client);
this.webRequestCache.Add(tileInfo.Quadkey, uri);
//client.DownloadDataCompleted += TileDownloadDataCompleted;
//client.DownloadDataAsync(uri, tileInfo);
tileInfo.Content = client.DownloadData(uri);
}
}
}
}
Note that this is just a sample approach and it may not cover all possible cases. Feel free to modify it in a way and extend in way which suits your requirements best.
I hope this information helps.
Regards,
Dess | Tech Support Engineer, Sr.
Progress Telerik
Many thanks, this approach works quite well with Openstreetmaps. It appears, however, that Bing may require a different approach.
Is there also any way to "snapshot" the other MapVisualElements like the map scale indicator and the legend if they are displayed?
Thanks
Hello, Yemo,
If you want to take a snapshot of RadMap as it is displayed together with the legend, you can use the printable panel approach which is demonstrated in the following article: https://docs.telerik.com/devtools/winforms/telerik-presentation-framework/printing-support/how-to/create-prinatble-panel
I hope this information helps.
Regards,
Dess | Tech Support Engineer, Sr.
Progress Telerik
Our thoughts here at Progress are with those affected by the outbreak.
Dess,
Thanks for your response, but I should have been more specific. Using the approach you outlined in the code sample:
element.Paint(gg, this.radMap1.MapElement);
will draw all the background tiles to the bitmap being saved.
this.radMap1.MapElement.Layers("PinsLayer").Paint(gg, this.radMap1.MapElement)
will draw the "PinsLayer" to the bitmap being saved.
Is it possible to get an hdc or image of the this.radMap1.MapElement.ScaleIndicatorElement and the radMap1.MapElement.Legendelement to draw on the same bitmap?Thanks
I have modified the code snippet as it is illustrated below in order to include the scale indicator and the legend:
static void Main(string[] args)
{
RadMap radMap1 = new RadMap();
radMap1.CreateControl();
string cacheFolder = @"..\..\cache";
OpenStreetMapProvider osmProvider = new OpenStreetMapProvider();
LocalFileCacheProvider cache = new LocalFileCacheProvider(cacheFolder);
osmProvider.CacheProvider = cache;
osmProvider.TileDownloader = new CustomTileDownLoader();
MapTileDownloader tileDownloader = osmProvider.TileDownloader as MapTileDownloader;
tileDownloader.WebHeaders.Add(System.Net.HttpRequestHeader.UserAgent, "your application name");
radMap1.MapElement.Providers.Add(osmProvider);
radMap1.ShowNavigationBar = true;
radMap1.ShowSearchBar = true;
radMap1.ShowScaleIndicator = true;
radMap1.LoadElementTree();
radMap1.Providers[0].Initialize();
osmProvider.ViewportChanged(radMap1.MapElement, ViewportChangeAction.All);
Bitmap bmp = new Bitmap(radMap1.Width, radMap1.Height);
radMap1.DrawToBitmap(bmp, new Rectangle(Point.Empty, radMap1.Size));
bmp.Save (@"..\..\MapTest.png");
Process.Start(@"..\..\testTest.png");
}
public class CustomTileDownLoader : MapTileDownloader
{
public override void BeginDownloadTile(Uri uri, TileInfo tileInfo)
{
lock (this.webClientsPoolLockObject)
{
if (!this.webClientsPool.ContainsKey(tileInfo.Quadkey))
{
WebClient client = new WebClient();
foreach (string key in this.WebHeaders.AllKeys)
{
client.Headers.Add(key, this.WebHeaders[key]);
}
this.webClientsPool.Add(tileInfo.Quadkey, client);
this.webRequestCache.Add(tileInfo.Quadkey, uri);
//client.DownloadDataCompleted += TileDownloadDataCompleted;
//client.DownloadDataAsync(uri, tileInfo);
tileInfo.Content = client.DownloadData(uri);
}
}
}
}
I hope this information helps.
Regards,
Dess | Tech Support Engineer, Sr.
Progress Telerik
Our thoughts here at Progress are with those affected by the outbreak.
Dess,
Many thanks for your response. The scale indicator now shows but, unfortunately, the legend always shows up as a blank rectangle, as in your screenshot,no matter how many items you add to it.
Thanks
Hello, Yemo,
Here is an alternative approach for saving the map to a bitmap where the legend items are rendered in the bitmap as well:
RadMap radMap1 = new RadMap();
radMap1.CreateControl();
string cacheFolder = @"..\..\cache";
OpenStreetMapProvider osmProvider = new OpenStreetMapProvider();
LocalFileCacheProvider cache = new LocalFileCacheProvider(cacheFolder);
osmProvider.CacheProvider = cache;
osmProvider.TileDownloader = new CustomTileDownLoader();
MapTileDownloader tileDownloader = osmProvider.TileDownloader as MapTileDownloader;
tileDownloader.WebHeaders.Add(System.Net.HttpRequestHeader.UserAgent, "your application name");
radMap1.MapElement.Providers.Add(osmProvider);
radMap1.ShowNavigationBar = true;
radMap1.ShowSearchBar = true;
radMap1.ShowScaleIndicator = true;
radMap1.ShowLegend = true;
radMap1.MapElement.LegendElement.TitleElement.Text = "NBA";
radMap1.MapElement.LegendElement.SubtitleElement.Text = "Conferences";
radMap1.MapElement.LegendElement.Orientation = Orientation.Horizontal;
radMap1.MapElement.LegendElement.ItemStackElement.Children.Add(new MapLegendItemElement("Western", Color.Red));
radMap1.MapElement.LegendElement.ItemStackElement.Children.Add(new MapLegendItemElement("Eastern", Color.Blue));
radMap1.LoadElementTree(new Size(1000, 1000));
radMap1.Providers[0].Initialize();
radMap1.MapElement.LegendElement.Initialize();
osmProvider.ViewportChanged(radMap1.MapElement, ViewportChangeAction.All);
radMap1.MapElement.InvalidateMeasure(true);
radMap1.MapElement.UpdateLayout();
Application.DoEvents();
var s = radMap1.MapElement.LegendElement.DesiredSize;
Bitmap b = radMap1.MapElement.GetAsBitmap(Brushes.Red, 0, new SizeF(1, 1));
b.Save(@"..\..\MapTest.png");
Process.Start(@"..\..\MapTest.png");
I believe that it would be suitable for you.
Regards,
Dess | Tech Support Engineer, Sr.
Progress Telerik
Our thoughts here at Progress are with those affected by the outbreak.
Dess,
Many thanks for the continued, patient support on this thread.
The DoEvents is not required as the single line radMap1.MapElement.UpdateLayout(); fixed the issue as displayed in the attached bitmap. You may wish to consider adding the modified code and other comments to the downloadable code sample which demonstrates how to capture the display.
Not sure if these extra comments are relevant to thi thread, but from your demonstrated code sample the only parts which are missing are:
1. The line LocalFileCacheProvider cache = new LocalFileCacheProvider(cacheFolder); does not cache locally stored tiles. Can you confirm?
2. The entire CustomTileDownLoader() class does not work with the BingRestMapProvider. It would be great if you could demonstrate this functionality and also possibly a generic functionality that accepts a URLTemplate as an argument as in the Telerik.Reporting.GenericTileProvider eg '.UrlTemplate = "http://{subdomain}.tile.thunderforest.com/cycle/{zoom}/{x}/{y}.png?apikey=7xxx"
Many thanks
Hello, Yemo,
Thank you for the provided feedback. We will consider preparing a Knowledge Base article on this topic in order other customers to benefit from it. However, currently the forum post is public enough for this purpose.
Straight to your questions:
1. Indeed, when using the custom TileDownloader, the tiles are not cached locally. In the OnTileDownloadComplete method of the OpenStreetMapProvider, the tile images are stored in the local folder. It is triggered when the TileDownloader.TileDownloadComplete event is fired. However, if you have a look at the custom TileDownloader, you will notice that we don't use the asynchronous logic for downloading the tiles which is used by default in a normal situation. But we force downloading the necessary map tiles at once. I have slightly modified the sample code snippet in a way to enable the caching option:
static void Main(string[] args)
{
RadMap radMap1 = new RadMap();
radMap1.CreateControl();
string cacheFolder = @"..\..\cache";
OpenStreetMapProvider osmProvider = new OpenStreetMapProvider();
LocalFileCacheProvider cache = new LocalFileCacheProvider(cacheFolder);
osmProvider.EnableCaching = true;
osmProvider.CacheProvider = cache;
osmProvider.TileDownloader = new CustomTileDownLoader(osmProvider);
MapTileDownloader tileDownloader = osmProvider.TileDownloader as MapTileDownloader;
tileDownloader.WebHeaders.Add(System.Net.HttpRequestHeader.UserAgent, "your application name");
radMap1.MapElement.Providers.Add(osmProvider);
radMap1.ShowNavigationBar = true;
radMap1.ShowSearchBar = true;
radMap1.ShowScaleIndicator = true;
radMap1.ShowLegend = true;
radMap1.MapElement.LegendElement.TitleElement.Text = "NBA";
radMap1.MapElement.LegendElement.SubtitleElement.Text = "Conferences";
radMap1.MapElement.LegendElement.Orientation = Orientation.Horizontal;
radMap1.MapElement.LegendElement.ItemStackElement.Children.Add(new MapLegendItemElement("Western", Color.Red));
radMap1.MapElement.LegendElement.ItemStackElement.Children.Add(new MapLegendItemElement("Eastern", Color.Blue));
radMap1.LoadElementTree(new Size(1000, 1000));
radMap1.Providers[0].Initialize();
radMap1.MapElement.LegendElement.Initialize();
osmProvider.ViewportChanged(radMap1.MapElement, ViewportChangeAction.All);
radMap1.MapElement.InvalidateMeasure(true);
radMap1.MapElement.UpdateLayout();
var s = radMap1.MapElement.LegendElement.DesiredSize;
Bitmap b = radMap1.MapElement.GetAsBitmap(Brushes.Red, 0, new SizeF(1, 1));
b.Save(@"..\..\MapTest.png");
Process.Start(@"..\..\MapTest.png");
}
public class CustomTileDownLoader : MapTileDownloader
{
private OpenStreetMapProvider osmProvider;
public CustomTileDownLoader(OpenStreetMapProvider osmProvider)
{
this.osmProvider = osmProvider;
}
public override void BeginDownloadTile(Uri uri, TileInfo tileInfo)
{
lock (this.webClientsPoolLockObject)
{
if (!this.webClientsPool.ContainsKey(tileInfo.Quadkey))
{
WebClient client = new WebClient();
foreach (string key in this.WebHeaders.AllKeys)
{
client.Headers.Add(key, this.WebHeaders[key]);
}
this.webClientsPool.Add(tileInfo.Quadkey, client);
this.webRequestCache.Add(tileInfo.Quadkey, uri);
//client.DownloadDataCompleted += TileDownloadDataCompleted;
//client.DownloadDataAsync(uri, tileInfo);
tileInfo.Content = client.DownloadData(uri);
if (osmProvider.EnableCaching && osmProvider.CacheProvider != null)
{
osmProvider.CacheProvider.Save(GetCacheKey(tileInfo.TileX, tileInfo.TileY, tileInfo.ZoomLevel), DateTime.MaxValue, tileInfo.Content);
}
}
}
}
private string GetCacheKey(int tileX, int tileY, int zoomLevel)
{
return "Tile_" + tileX + "_" + tileY + "_" + zoomLevel + ".png";
}
}
2. Regarding the BingRestMapProvider, it loads meta data asynchronously. That is why the previous solution is not applicable to it.
As a gesture of good will, I have invested some more time to implement the silent extracting to an image when using the bing provider. You can find below the code snippet. Note that this is just a sample approach and it may not cover all possible cases. Feel free to modify and extend it further in a way which suits your requirements best.
RadMap radMap1 = new RadMap();
radMap1.CreateControl();
CustomBingProvider bingProvider = new CustomBingProvider();
bingProvider.UseSession = false;
bingProvider.BingKey = bingKey;
bingProvider.TileDownloader = new CustomTileDownLoader(bingProvider);
radMap1.Providers.Add(bingProvider);
bingProvider.EnableCaching = false;
radMap1.MapElement.Providers.Add(bingProvider);
radMap1.ShowNavigationBar = true;
radMap1.ShowSearchBar = true;
radMap1.ShowScaleIndicator = true;
radMap1.ShowLegend = true;
radMap1.MapElement.LegendElement.TitleElement.Text = "NBA";
radMap1.MapElement.LegendElement.SubtitleElement.Text = "Conferences";
radMap1.MapElement.LegendElement.Orientation = Orientation.Horizontal;
radMap1.MapElement.LegendElement.ItemStackElement.Children.Add(new MapLegendItemElement("Western", Color.Red));
radMap1.MapElement.LegendElement.ItemStackElement.Children.Add(new MapLegendItemElement("Eastern", Color.Blue));
radMap1.LoadElementTree(new Size(1000, 1000));
radMap1.Providers[0].Initialize();
radMap1.MapElement.LegendElement.Initialize();
bingProvider.ViewportChanged(radMap1.MapElement, ViewportChangeAction.All);
radMap1.MapElement.InvalidateMeasure(true);
radMap1.MapElement.UpdateLayout();
var s = radMap1.MapElement.LegendElement.DesiredSize;
Bitmap b = radMap1.MapElement.GetAsBitmap(Brushes.Red, 0, new SizeF(1, 1));
b.Save(@"..\..\MapTest.png");
Process.Start(@"..\..\MapTest.png");
public class CustomBingProvider : BingRestMapProvider
{
private ImageryMetadata tileMetadataInfo;
private const string ImageryMetadataServiceUri = "https://dev.virtualearth.net/REST/v1/Imagery/Metadata/{set}?output=json&key={key}&c={culture}&dir={directory}";
protected override void InitializeImageryService()
{
this.tileMetadataInfo = null;
try
{
string uriString = ImageryMetadataServiceUri;
uriString = uriString.Replace("{set}", this.ImagerySet.ToString());
uriString = uriString.Replace("{key}", string.IsNullOrEmpty(this.SessionId) ? this.BingKey : this.SessionId);
uriString = uriString.Replace("{culture}", this.Culture.ToString());
uriString = uriString.Replace("{directory}", "0");
WebClient client = new WebClient();
//client.DownloadStringCompleted += this.InitializeImageryMetadataCompleted;
//client.DownloadStringAsync(new Uri(uriString, UriKind.Absolute));
string result = client.DownloadString(new Uri(uriString, UriKind.Absolute));
InitializeImageryMetadataCompleted(client, result);
}
catch (Exception ex)
{
throw new Exception(string.Format("Imagery Service Exception: {0}", ex.Message));
}
}
protected virtual void InitializeImageryMetadataCompleted(object sender, string result)
{
this.tileMetadataInfo = null;
WebClient client = sender as WebClient;
// client.DownloadStringCompleted -= InitializeImageryMetadataCompleted;
try
{
#if NET2
using (StringReader reader = new StringReader(result))
{
JsonSerializer serializer = new JsonSerializer();
ImageryMetadataResponse response = serializer.Deserialize<ImageryMetadataResponse>(new JsonTextReader(reader));
if (response != null && response.ResourceSets.Length > 0 && response.ResourceSets[0].Resources.Length > 0)
{
this.tileMetadataInfo = response.ResourceSets[0].Resources[0];
if (this.tileMetadataInfo.ImageUrl.StartsWith("http://") && this.ImagerySet == ImagerySet.OrdnanceSurvey)
{
this.tileMetadataInfo.ImageUrl = this.tileMetadataInfo.ImageUrl.Replace("http://", "https://");
}
this.tileSize = new Size(int.Parse(this.tileMetadataInfo.ImageWidth), int.Parse(this.tileMetadataInfo.ImageHeight));
this.MinZoomLevel = tileMetadataInfo.ZoomMin;
this.MaxZoomLevel = tileMetadataInfo.ZoomMax;
}
}
#else
using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(result)))
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(ImageryMetadataResponse));
ImageryMetadataResponse response = serializer.ReadObject(stream) as ImageryMetadataResponse;
if (response != null && response.ResourceSets.Length > 0 && response.ResourceSets[0].Resources.Length > 0)
{
this.tileMetadataInfo = response.ResourceSets[0].Resources[0];
FieldInfo fi = typeof(BingRestMapProvider).GetField("tileSize", BindingFlags.Instance | BindingFlags.NonPublic);
fi.SetValue(this, new System.Drawing.Size(int.Parse(this.tileMetadataInfo.ImageWidth), int.Parse(this.tileMetadataInfo.ImageHeight)));
// this.tileSize = new System.Drawing.Size(int.Parse(this.tileMetadataInfo.ImageWidth), int.Parse(this.tileMetadataInfo.ImageHeight));
this.MinZoomLevel = tileMetadataInfo.ZoomMin;
this.MaxZoomLevel = tileMetadataInfo.ZoomMax;
}
}
#endif
}
#if NET2
catch (JsonSerializationException) { }
#else
catch (SerializationException) { }
#endif
FieldInfo ti = typeof(BingRestMapProvider).GetField("tileMetadataInfo", BindingFlags.Instance | BindingFlags.NonPublic);
ti.SetValue(this, this.tileMetadataInfo);
this.Initialized = true;
this.OnInitializationComplete(EventArgs.Empty);
}
}
public class CustomTileDownLoader : MapTileDownloader
{
private IMapTileProvider provider;
public CustomTileDownLoader(IMapTileProvider provider)
{
this.provider = provider;
}
public override void BeginDownloadTile(Uri uri, TileInfo tileInfo)
{
lock (this.webClientsPoolLockObject)
{
if (!this.webClientsPool.ContainsKey(tileInfo.Quadkey))
{
WebClient client = new WebClient();
foreach (string key in this.WebHeaders.AllKeys)
{
client.Headers.Add(key, this.WebHeaders[key]);
}
this.webClientsPool.Add(tileInfo.Quadkey, client);
this.webRequestCache.Add(tileInfo.Quadkey, uri);
//client.DownloadDataCompleted += TileDownloadDataCompleted;
//client.DownloadDataAsync(uri, tileInfo);
tileInfo.Content = client.DownloadData(uri);
if (provider.EnableCaching && provider.CacheProvider != null)
{
provider.CacheProvider.Save(GetCacheKey(tileInfo.TileX, tileInfo.TileY, tileInfo.ZoomLevel), DateTime.MaxValue, tileInfo.Content);
}
}
}
}
private string GetCacheKey(int tileX, int tileY, int zoomLevel)
{
return "Tile_" + tileX + "_" + tileY + "_" + zoomLevel + ".png";
}
}
As to the question about generic templates, this seems to be more like a general programming question and I would like to ask you to use forums like MSDN, StackOverflow, etc. for such questions. Once you have the downloaded images from any resource, you can use the LocalMapProvider that RadMap offers: https://docs.telerik.com/devtools/winforms/controls/map/providers/localmapprovider
I hope this information helps.
Regards,
Dess | Tech Support Engineer, Sr.
Progress Telerik
Our thoughts here at Progress are with those affected by the outbreak.
Many, many thanks Dess. Capturing the display/Saving an image from the Bing provider works.
Also noticed that elsewhere in this forum you answered a question on a GoogleProvider which provided a code snippet that addresses the question I raised above about a generic template for other tile providers.
Thanks
Hi, Yemo,
I am glad that the provided code snippet for the Bing provider was helpful.
As to the Goggle maps, I am posting the link to the forum thread here in order the community to benefit from it: https://www.telerik.com/forums/support-for-google-maps-eccbc827c389
However, it is important to note that RadMap does not have a Google Maps provider and currently such cannot be implemented because of the additional protection which Google implemented in their service. Using the Google Maps tile server in a WinForms application is not allowed. But, in case that your customer has a license for using Google API, you can use the solution in the referred forum post.
Thank you for your understanding.
Regards,
Dess | Tech Support Engineer, Sr.
Progress Telerik
Our thoughts here at Progress are with those affected by the outbreak.