Tutorial: Basic high-quality web map printing/exporting using arcpy.mapping

Complexity: Intermediate Data Requirement: ArcGIS.com

In this tutorial, you will build a web application where the end user will be able to navigate to an area of interest and press a Print button. The output will be a printer-friendly PDF document containing high-quality vector output for service layers.

By default, exporting from a web application using the out-of-the-box Printing Tools service and its client print widgets in the ArcGIS web APIs outputs an image of the service layers, which may be at a resolution too low (for example, 96 dpi) for large-format printing or high-quality cartographic output. This is especially true if your service layers use a tiled map cache. In this case, the output of the service layers will be a tiled cached image.

The illustration below shows two sample output PDF files. The image on the left shows what a tiled cache of service layers may look like if you export to a large-format PDF and you are zoomed in. Note the blurriness of the features and the pixelated text. This may look fine at a smaller scale or a cached scale. It may also look okay at letter or tabloid size—but probably not bigger.

The image on the right shows vector data. It is much smoother and would be more appropriate for high-quality cartographic output. This tutorial will show you how to customize the out-of-the-box printing solution to output a printer-friendly PDF document containing high-quality vector output for service layers.

Low-versus high-resolution sample output

In the web application that you will build, you will output vector equivalents of the World Topographic basemap service. A vector subset of the World Topographic basemap service is available on the ArcGIS Resource Center.

NoteNote:

In order to generate an output document containing high-quality local vector data instead of a tiled cache image of service layers, you must have access to the vector data.

The illustration below shows the web application that you will build.

Web application

The code behind the Print button in the web application uses the ArcGIS web APIs' Print Task, which is available starting with ArcGIS 10.1 for Server. You will create a Python script that will be published as a geoprocessing service that the Print Task will use. The Python script uses the ConvertWebMapToMapDocument function in the arcpy.mapping module which will insert the full state of the web map into a staged template map document. The template contains vector equivalents of all the possible layers in the map service. The arcpy.mapping module also provides functionality to remove service layers, leaving local vector data that was staged in the template map document and export to PDF.

After clicking the Print button, the output will be a printer-friendly PDF document containing high-quality local vector data instead of a tiled cache image of service layers.

To complete this tutorial, you should be familiar with the arcpy.mapping module, the ArcGIS web APIs, ArcGIS for Desktop, and ArcGIS for Server. Sample code for all the ArcGIS web APIs are included in this tutorial. You should also become familiar with printing in the web application help topics:

Printing in web applicationsAdvanced printing for web maps

Get the vector data from the ArcGIS Resource Center

You will download a zip file from the Community Maps Template Gallery on the ArcGIS Resource Center that contains the vector data that will be used in the staged map document template.

Before you do that, you need to make sure that a folder structure exists where ArcGIS for Server can see the template map document and data that will be used in the web application. This tutorial assumes you have a folder that is registered with ArcGIS for Server. For more information on registering data with ArcGIS for Server, see:

TipTip:

When using template map documents in the ConvertWebMapToMapDocument function, the best practice is to use data that is registered with ArcGIS for Server. If you choose to not use registered data, template map documents and data will be packaged and copied to the server. During packaging, data may be moved and resourced with relative paths to a folder structure that ConvertWebMapToMapDocument cannot resolve. For more information, see the ConvertWebMapToMapDocument help topic.

Steps:
  1. Open a new, empty ArcMap session.
  2. In the Catalog window, navigate to your registered folder. Create a new folder in the registered folder named BasicTutorial.
  3. Go to the Community Maps Template Gallery on the ArcGIS Resource Center. Select the World Topographic Map Template.
  4. Download the World Topographic Map Template zip file to your computer by clicking the Download button.
  5. Extract the contents of the zip file to the BasicTutorial folder in your registered folder.
  6. Your registered folder should look similar to the illustration below.
    Registered folder
    NoteNote:

    In the screen capture above, the registered folder is named MyDataStore. Your registered folder can be named anything.

The LGIM_World_Topo_Map_v1.5 map document is now ready to be used in the web application.

Create the Python script

You will create a Python script that will be used as the custom geoprocessing print service.

The Python script in the custom geoprocessing print service executes the ConvertWebMapToMapDocument function which converts a web map (in JSON format) that you intend to print or export to a map document. The script then removes the service layers in the output map document, leaving the vector layers that correspond to the service layer in the web map JSON. The web map JSON also contains the extent of the map in the web application. Finally, the script exports the map document to PDF.

Steps:
  1. Open any Python IDE, such as IDLE (which comes with ArcGIS for Desktop).
  2. Copy and paste the following code into a new Python script.
  3. import arcpy
    import os
    import uuid
    
    # Input WebMap json
    Web_Map_as_JSON = arcpy.GetParameterAsText(0)
    
    # The template location in the server data store
    templateMxd = '//MyComputer/MyDataStore/BasicTutorial/LG_062912_10.1/LG_062912_10.1/LGIM_World_Topo_Map_v1.5.mxd'
       
    # Convert the WebMap to a map document
    result = arcpy.mapping.ConvertWebMapToMapDocument(Web_Map_as_JSON, templateMxd)
    mxd = result.mapDocument
    
    # Reference the data frame that contains the webmap
    # Note: ConvertWebMapToMapDocument renames the active dataframe in the template_mxd to "Webmap"
    df = arcpy.mapping.ListDataFrames(mxd, 'Webmap')[0]
    
    # Remove the service layer
    # This will just leave the vector layers from the template
    for lyr in arcpy.mapping.ListLayers(mxd, data_frame=df):
        if lyr.isServiceLayer:
            arcpy.mapping.RemoveLayer(df, lyr)
            
    # Use the uuid module to generate a GUID as part of the output name
    # This will ensure a unique output name
    output = 'WebMap_{}.pdf'.format(str(uuid.uuid1()))
    Output_File = os.path.join(arcpy.env.scratchFolder, output)
    
    # Export the WebMap
    arcpy.mapping.ExportToPDF(mxd, Output_File) 
    
    # Set the output parameter to be the output file of the server job
    arcpy.SetParameterAsText(1, Output_File)
    
    # Clean up - delete the map document reference
    filePath = mxd.filePath
    del mxd, result
    os.remove(filePath)
    
  4. Change the templateMxd variable to be the UNC path to the folder in your registered folder that contains the template map documents.
  5. NoteNote:

    If the ArcGIS for Server, ArcGIS for Desktop, and the registered folder are all on the same machine, then UNC paths to the registered folder are not required. Instead, absolute paths can be used.

  6. Save the Python script. Name the script BasicTutorial.py. Save it in a folder named WebApp within the registered folder.

Create a Python script tool

You will create a custom geoprocessing tool that runs the BasicTutorial.py script.

Steps:
  1. In the Catalog window in ArcMap, navigate to the WebApp folder in your registered folder directory.
  2. Right-click the WebApp folder and click New > Toolbox. Name the toolbox BasicTutorial.
  3. Right-click the BasicTutorial toolbox and click Item Description.
  4. In the Item Description dialog box, populate in the Tags and Summary items with the text of your choice. Optionally, fill in other item descriptions.
  5. Click Save and exit the Item Description dialog box.
  6. In the Catalog window, right-click the BasicTutorial toolbox and click Add > Script.
  7. In the Add Script dialog box, enter BasicTutorial for both the Name and the Label.
  8. Click Next.
  9. For the Script File, navigate to the WebApp folder in the registered folder and select BasicTutorial.py.
  10. Click Next.
  11. Two parameters need to be added to the script tool.
    1. The first one will be Web_Map_as_JSON. This parameter takes in a JSON representation of the state of the map to be exported as it appears in the web application. The properties should match the following screen capture:
      Web_Map_as_JSON parameter properties
    2. The next parameter is Output_File—the output file that will be created. The properties should match the following screen capture:
      Output_File parameter properties

      CautionCaution:

      The Web_Map_as_JSON and Output_File parameter names must be spelled exactly as shown so that they match the tool signature of the Print Task in the ArcGIS web APIs.

    3. Click Finish on the Add Script dialog box.
  12. Right-click the BasicTutorial script tool and click Item Description.
  13. In the Item Description dialog box, populate the Tags and Summary items with the text of your choice. Also populate the Dialog Explanation for all four parameters in the Syntax section of the Item Description dialog box with the text of your choice. Optionally, fill in other item descriptions.

Execute the tool

The tool needs to be executed successfully to create a result in the Results window that can be published to ArcGIS for Server.

Steps:
  1. In the Catalog window, right-click the BasicTutorial script tool and click Open.
  2. Leave the Web_Map_as_JSON input parameter blank.
    TipTip:

    For publishing purposes, you can leave the Web_Map_as_JSON input parameter blank as the ArcGIS web APIs will provide the web map JSON in the web application. You can leave the Web_Map_as_JSON input parameter blank provided the Python script was written in such a way as to not fail with blank input. For example, the script doesn't look for web map layers by name.

  3. Click OK. Wait for the tool to finish executing.

The result is now ready to be published as a geoprocessing service.

Publish the result

If you are unfamiliar with publishing geoprocessing services, see:

Steps:
  1. Open the Results window.
  2. Expand Current Session.
  3. Right-click the BasicTutorial result and click Share As > Geoprocessing Service.
  4. Check Publish a service.
  5. Click Next.
  6. Choose a publish or admin connection to your ArcGIS for Server machine.
  7. Click Next.
  8. Click Next.
  9. Click Continue.
  10. In the top right corner of the Service Editor dialog box, click Publish.

The geoprocessing service is now ready to be used in the ArcGIS web APIs.

Choose which ArcGIS web API you want to use to create the web application

The ArcGIS web APIs offer similar GIS functionality; it is up to you to choose your preferred developer platform. It is beyond the scope of this tutorial to go into detail about getting started building web applications with each API. Please consult the API documentation for that. For more information, see:

Each ArcGIS web API has a Print Task. The Print Task has a URL property which will reference the custom geoprocessing service that you created earlier. Please consult the ArcGIS web API documentation for more information on the Print Task.

In the sections below, use the code samples for the ArcGIS web API that you work in to build your web application.

ArcGIS API for JavaScript sample code

If you are using the ArcGIS API for JavaScript, use the following sample code to build your web application.

In the ArcGIS API for JavaScript code sample below, change the URL to the geoprocessing service that you created in a previous step to match your server name. It is referenced on this line:

var printUrl = "http://MyServer:6080/arcgis/rest/services/BasicTutorial/GPServer/BasicTutorial";

Code for BasicTutorial.html:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
  	<title>Webmap Printing</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=7,IE=9" />
    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
    <link rel="stylesheet" type="text/css" href="http://serverapi.arcgisonline.com/jsapi/arcgis/3.0/js/dojo/dijit/themes/claro/claro.css">  
    <script src="http://serverapi.arcgisonline.com/jsapi/arcgis/?v=3.0"></script>
    <script type="text/javascript" language="Javascript">
      dojo.require("esri.map");
      dojo.require("esri.tasks.PrintTask");
      var printTask, params;
      
      function init() {
		      // set the extent to the Naperville area - which matches the extent of the template map document
        var startExtent = new esri.geometry.Extent({"xmin":-9815098,"ymin":5124020, "xmax":-9811168,"ymax":5127339, "spatialReference":{"wkid":102100}});
        											
        var map = new esri.Map("map", {extent:startExtent});
        
        // add tiled map service to webmap
        var tiledUrl = "http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer";
        var tiledLayer = new esri.layers.ArcGISTiledMapServiceLayer(tiledUrl);
        map.addLayer(tiledLayer);
		      var printUrl = "http://MyServer:6080/arcgis/rest/services/BasicTutorial/GPServer/BasicTutorial";
		      printTask = new esri.tasks.PrintTask(printUrl, {async: true});
		      params = new esri.tasks.PrintParameters();
        params.map = map;  
      }
      
      function print(){
      	var ptemplate = new esri.tasks.PrintTemplate();
      	// use the extent of the webmap in the output PDF
      	ptemplate.preserveScale = false;
      	params.template = ptemplate;
        printTask.execute(params, printComplete);
      }
      
      function printComplete(result){
        window.open(result.url);
      }

      dojo.addOnLoad(init);

    </script>
  </head>
  <body class="claro">
  	 <input type="button" id="print" value="Print" onclick="print();"/>
    <div id="map" style="width:1000px; height:600px; border:1px solid #000;"></div>
  </body>
</html>

ArcGIS API for Flex sample code

If you are using the ArcGIS API for Flex, use the following sample code to build your web application.

In the ArcGIS API for Flex code sample below, change the URL to the geoprocessing service that you created in a previous step to match your server name. It is referenced on these lines:

url="http://MyServer:6080/arcgis/rest/services/BasicTutorial/GPServer/BasicTutorial"/>

Code for BasicTutorial.mxml:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:esri="http://www.esri.com/2008/ags"
			   initialize="printTask.getServiceInfo()"
			   pageTitle="High-quality printing">
	
	<fx:Script>
		<![CDATA[
			import com.esri.ags.events.PrintEvent;
			import com.esri.ags.tasks.supportClasses.DataFile;
			import com.esri.ags.tasks.supportClasses.JobInfo;
			import com.esri.ags.tasks.supportClasses.ParameterValue;
			
			import mx.controls.Alert;
			
			public var preserveScale:Boolean = true;
			
			private function printBtn_clickHandler(event:MouseEvent):void
			{
				if (printTask.getServiceInfoLastResult.isServiceAsynchronous)
				{
					printTask.submitJob(printParameters);
				}
				else
				{
					printTask.execute(printParameters);
				}
			}
			
			private function printTask_jobCompleteHandler(event:PrintEvent):void
			{
				var jobInfo:JobInfo = event.jobInfo;
				if (jobInfo.jobStatus == JobInfo.STATUS_SUCCEEDED)
				{
					printTask.getResultData(jobInfo.jobId);
				}
				else
				{
					Alert.show(jobInfo.jobStatus);
				}
			}
			
			private function printTask_getResultDataCompleteHandler(event:PrintEvent):void
			{
				var dataFile:DataFile = event.parameterValue.value as DataFile;
				navigateToURL(new URLRequest(dataFile.url));
			}
			
			private function printTask_executeCompleteHandler(event:PrintEvent):void
			{
				var paramValue:ParameterValue = event.executeResult.results[0];
				var dataFile:DataFile = paramValue.value as DataFile;
				navigateToURL(new URLRequest(dataFile.url));
			}
		]]>
	</fx:Script>
	
	<fx:Declarations>
		<esri:PrintTask id="printTask"
						executeComplete="printTask_executeCompleteHandler(event)"
						fault="Alert.show(event.fault.faultString)"
						getResultDataComplete="printTask_getResultDataCompleteHandler(event)"
						jobComplete="printTask_jobCompleteHandler(event)"
						showBusyCursor="false"
						url="http://MyServer:6080/arcgis/rest/services/BasicTutorial/GPServer/BasicTutorial"/>
		<esri:PrintParameters id="printParameters"
							  preserveScale="false"
							  map="{map}">
		</esri:PrintParameters>
		
		<esri:Extent id="initialExtent"
					 xmin="-9815098" ymin="5124020" xmax="-9811168" ymax="5127339">
			<esri:SpatialReference wkid="102100"/>
		</esri:Extent>
		
	</fx:Declarations>
	
	<s:controlBarLayout>
		<s:HorizontalLayout gap="10"
							paddingBottom="7"
							paddingLeft="10"
							paddingRight="10"
							paddingTop="7"
							verticalAlign="baseline"/>
	</s:controlBarLayout>
	<s:controlBarContent>
		<s:Button id="printBtn"
				  click="printBtn_clickHandler(event)"
				  enabled="{printTask.getServiceInfoLastResult != null}"
				  label="Print"/>
	</s:controlBarContent>

	<esri:Map id="map" extent="{initialExtent}">
		
		<esri:ArcGISTiledMapServiceLayer url="http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer"/>
	</esri:Map>
	
</s:Application>

ArcGIS API for Silverlight sample code

If you are using the ArcGIS API for Silverlight, use the following sample code to build your web application.

XAML code:

<UserControl x:Class="Basic_Tutorial.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:esri="http://schemas.esri.com/arcgis/client/2009">

    <Grid x:Name="LayoutRoot" Background="White">

        <esri:Map x:Name="MyMap" Extent="-9815098,5124020,-9811168,5127339">
            <esri:Map.Layers>
                <esri:ArcGISTiledMapServiceLayer ID="MyTiledService" 
                    Url="http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer"/>
            </esri:Map.Layers>
        </esri:Map>

        <StackPanel Orientation="Vertical" Margin="15" HorizontalAlignment="Left" VerticalAlignment="Top" >
            <Border x:Name="PrintPanel" CornerRadius="10" BorderBrush="Gray" BorderThickness="1" Background="White" HorizontalAlignment="Left" 
                    VerticalAlignment="Top" Margin="15" Padding="10">
                <Border.Effect>
                    <DropShadowEffect/>
                </Border.Effect>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <Button x:Name="Print" Content="Print" Click="ExportMap_Click" 
                            Grid.Row="1" Grid.Column="0" Margin="2"/>
                </Grid>
            </Border>
        </StackPanel>
    </Grid>
</UserControl>

In the ArcGIS API for Silverlight code sample below, change the URL to the geoprocessing service that you created in a previous step to match your server name. It is referenced on this line:

printTask = new PrintTask("http://MyServer:6080/arcgis/rest/services/BasicTutorial/GPServer/BasicTutorial");

Code behind in C#:

using System;
using System.Windows;
using System.Windows.Controls;
using ESRI.ArcGIS.Client.Printing;
using ESRI.ArcGIS.Client.Tasks;


namespace Basic_Tutorial
{
    public partial class MainPage : UserControl
    {
        PrintTask printTask;
        public MainPage()
        {
            InitializeComponent();
            printTask = new PrintTask("http://MyServer:6080/arcgis/rest/services/BasicTutorial/GPServer/BasicTutorial");
            printTask.DisableClientCaching = true;
            printTask.JobCompleted += new EventHandler<PrintJobEventArgs>(printTask_JobCompleted);
            printTask.GetServiceInfoCompleted += new EventHandler<ServiceInfoEventArgs>(printTask_GetServiceInfoCompleted);
            printTask.GetServiceInfoAsync();
        }

        void printTask_GetServiceInfoCompleted(object sender, ServiceInfoEventArgs e)
        {
            PrintPanel.DataContext = e.ServiceInfo;
        }


        void printTask_JobCompleted(object sender, PrintJobEventArgs e)
        {
            if (e.Error == null)
            {
                if (e.PrintResult != null)
                    System.Windows.Browser.HtmlPage.Window.Navigate(e.PrintResult.Url, "_blank");
            }
        }

        private void ExportMap_Click(object sender, RoutedEventArgs e)
        {

            if (printTask == null || printTask.IsBusy) return;

            PrintParameters printParameters = new PrintParameters(MyMap)
            {

            };
            printTask.SubmitJobAsync(printParameters);
        }
    }
}

Run the web application

Run the web application that you created in the previous step. Consult the ArcGIS web API documentation for instructions on running web applications, if necessary. A screen capture of the ArcGIS for JavaScript web API application is below.

Sample web application

Zoom in to an area of interest and click the Print button. The output PDF will automatically pop up after a moment. The output will be a printer-friendly PDF document containing high-quality local vector data that was staged in the layout templates instead of tiled cache images of service layers. This produces higher-quality cartographic output. See below for sample output:

Sample PDF output

This concludes the basic high-quality web map printing/exporting tutorial. For more advanced scenarios, see the Advanced high-quality web map printing/exporting tutorial.

Related Topics

12/18/2014