This post will describe a technique for exporting a map created with the ArcGIS API for Silverlight to a PDF document completely client-side (no server-side computing).
You will need:
- Microsoft Visual Studio 2010 (link)
- Microsoft Silverlight 4 Tools for Visual Studio 2010 (link)
- ESRI ArcGIS API for Silverlight (link)
- silverPDF from codeplex (link)
How to build a map that can be saved to PDF:
Step 1 – In VS2010, create a new Silverlight Application called “SilverlightApplication1”, accept all the default options.
Step 2 – Add a reference to the ESRI’s ArcGIS API for Silverlight and silverPDF from codeplex.
Step 3 – Add the following to MainPage.xaml
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:esri="http://schemas.esri.com/arcgis/client/2009"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<esri:Map x:Name="Map">
<esri:ArcGISTiledMapServiceLayer
Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer" />
</esri:Map>
<Button x:Name="ButtonPdf"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Margin="10,0,0,10"
IsEnabled="False"
Content="Create PDF"/>
</Grid>
</UserControl>
Step 4 – Add the following to MainPage.xaml.cs (code behind)
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Resources;
using ESRI.ArcGIS.Client;
using ESRI.ArcGIS.Client.Geometry;
using PdfSharp.Drawing;
using PdfSharp.Pdf;
namespace SilverlightApplication1 {
public partial class MainPage : UserControl {
public MainPage() {
InitializeComponent();
// Listen to "Create PDF" button click
this.ButtonPdf.Click += new RoutedEventHandler(this.Button_Click);
// Enable "Create PDF" button only after the Imagery layer has loaded
this.Map.Layers[0].Initialized += (s, e) => {
this.ButtonPdf.IsEnabled = true;
// Also, zoom to an adjusted full extent that fills the map canvas
this.Map.ZoomTo(this.Map.Layers[0].FullExtent(this.Map));
};
}
private void Button_Click(object sender, RoutedEventArgs e) {
// Create a Save As dialog
SaveFileDialog dialog = new SaveFileDialog();
dialog.Filter = "PDF|*.pdf";
// Show dialog. Exit if use closes dialog.
if (!dialog.ShowDialog().Value) { return; }
// Get the current tiled layer
ArcGISTiledMapServiceLayer tiled = this.Map.Layers[0]
as ArcGISTiledMapServiceLayer;
// Create a new dynamic layer from the same map service
ArcGISDynamicMapServiceLayer dynamic = new ArcGISDynamicMapServiceLayer() {
Url = tiled.Url,
ImageFormat = ArcGISDynamicMapServiceLayer.RestImageFormat.JPG
};
// When the dynamic layer has initialized create the in-memory PDF document
dynamic.Initialized += (a, b) => {
dynamic.GetUrl(
this.Map.Extent,
(int)this.Map.ActualWidth,
(int)this.Map.ActualHeight,
delegate(string url, int width, int height, Envelope extent) {
// Download a new image of identical to what is currently
// displayed in the map
WebClient webClient = new WebClient();
webClient.OpenReadCompleted += (c, f) => {
// Use the dispatcher to force execution in the UI thread
Dispatcher.BeginInvoke(delegate() {
// Create the PDF document.
// Set document information properties.
PdfDocument document = new PdfDocument();
document.Info.Title = "World Imagery";
// Create a new page with the same dimensions as
// the browser map
PdfPage page = new PdfPage(document) {
Height = new XUnit(this.Map.ActualHeight,
XGraphicsUnit.Presentation),
Width = new XUnit(this.Map.ActualWidth,
XGraphicsUnit.Presentation)
};
document.Pages.Add(page);
// Create a graphics object for writing to the page
XGraphics graphics = XGraphics.FromPdfPage(page);
// Add the map image to the page
XImage map = XImage.FromStream(f.Result);
graphics.DrawImage(map, 0d, 0d);
// Save the PDF document to the user specified filename
document.Save(dialog.OpenFile());
// Inform the user that the PDF creation is complete
MessageBox.Show("PDF Creation Complete");
});
};
webClient.OpenReadAsync(new Uri(url));
}
);
};
dynamic.Initialize();
}
}
/// <summary>
/// Extends the Esri Layer class with an alternative FullExtent property.
/// Returns an adjusted extent conceptually similar to Image.Stretch==UniformToFill
/// </summary>
public static class LayerExtension {
public static Envelope FullExtent(this Layer layer, FrameworkElement parent) {
Envelope extent = layer.FullExtent;
double ratioMap = parent.ActualHeight / parent.ActualWidth;
double ratioLay = extent.Height / extent.Width;
if (ratioMap < ratioLay) {
return new Envelope() {
XMin = extent.XMin,
YMin = extent.GetCenter().Y - 0.5d * ratioMap * extent.Width,
XMax = extent.XMax,
YMax = extent.GetCenter().Y + 0.5d * ratioMap * extent.Width
};
}
return new Envelope() {
XMin = extent.GetCenter().X - 0.5d * extent.Height / ratioMap,
YMin = extent.YMin,
XMax = extent.GetCenter().X + 0.5d * extent.Height / ratioMap,
YMax = extent.YMax
};
}
}
}
The web application is now ready to run in debug. When the application loads, use your mouse (and mousewheel) to navigate to your favorite location on the planet. Click the “Create PDF” button to commence the PDF export. You will first be prompted for the output location of the PDF document and then again when the export is complete.
How are you referencing PDFSharp?
ReplyDeleteThe way this is written won't build
Thank you for the example. What do you do if I have multiple layers in a map? Thanks,
ReplyDeleteI have the same question
Delete@Anonymous. To support maps with multiple layers you would have to export each layer and combine with something like WriteableBitmap.
ReplyDeleteCan I ask you how to convert pdf to image file(jpg, png... etc)??
ReplyDeleteThank you
Seems like with the new 3.0 ESRI Silverlight API we can do away with the WebClient stuff. Now the map control does not cause any security exceptions when you call new WriteableBitmap(mapControl, null).
ReplyDelete