使用 arcpy.mp 进行 web 地图打印

Portal for ArcGIS 中常用的 arcpy.mp 工作流为 web 地图打印,在该工作流中,Python、ArcGIS API for JavaScriptArcGIS Web AppBuilder 三者结合使用以创建可产生高质量制图输出的 web 应用程序。

ConvertWebMapToArcGISProject

ConvertWebMapToArcGISProject 函数可将要打印或导出的 Web 地图转换为 ArcGIS Pro 工程。Web 地图转换后,工程中存在 Web 地图的完整状态。随即可对工程进行进一步修改,然后将其打印或导出为常用格式(如 PDF)。在使用 ArcGIS API for JavaScriptArcGIS Web AppBuilderweb GIS 应用程序打印地图时,通常会使用 ConvertWebMapToArcGISProject 函数。

arcpy.mp 在 web 地图打印中的优势

ConvertWebMapToArcGISProject 函数用于需要使用 arcpy.mp 函数修改或导出 web 地图的工作流。可以使用 ConvertWebMapToArcGISProject 函数处理的部分工作流示例如下:

  • 交换本地矢量数据的服务图层 - 在 arcpy.mp 脚本中,服务图层可以被识别出并与指向本地数据的图层进行交换。这通常用于首选矢量输出(而不是服务图层)。矢量输出的部分优势,如下所示:
    • 矢量 PDF 输出支持在 PDF 查看应用程序中嵌入要素属性。同时支持切换图层可见性和查看地图坐标,但无需矢量。
    • 动态表格图表可与过渡布局模板中的矢量图层相关联。它们会对地图范围方面的更改作出应对,以便仅显示地图中可见的数据。
    • 对于高质量制图输出,矢量数据可能更适用于缓存地图服务。
    完成交换矢量数据服务图层的一种方法是过渡布局模板,该布局模板包含所有可能服务图层的矢量等效表示。执行 ConvertWebMapToArcGISProject 函数后,依次执行输出地图中的所有图层,移除与 Web 地图内服务图层相对应的矢量图层以外的所有图层。当使用已拥有的相应矢量数据交换自身服务时,可以使用此工作流。有关代码示例,请参阅 ConvertWebMapToArcGISProject 中的示例 3。
  • 创建地图册 - 如果在过渡布局模板上启用空间地图系列,则可以生成地图册。也可以将输出布局导出为 PDF 文件,并使用 PDFDocument 类将其插入到其他 PDF 文件(例如,书名页或报告)中来创建完整的地图册。有关代码示例,请参阅 ConvertWebMapToArcGISProject 中的示例 5 和示例 6。
  • 创建报表 - 可以为 web 地图或过渡布局模板中的图层生成报表。完成此工作流的一种方法是将报表文件 (.rptx) 导入到从 ConvertWebMapToArcGISProject 返回的 ArcGIS Pro 工程中。使用 ArcGISProject 类中的 importDocument 函数导入报表文件。也可以使用 PDFDocument 类将报表追加到其他 PDF 文件(例如布局)。有关代码示例,请参阅 ConvertWebMapToArcGISProject 中的示例 7。
  • 使用高级选项导出 - 所有的 arcpy.mp 导出函数都具有高级选项。例如,针对 LayoutMapView 类的 exportToPDF 方法具有用于控制栅格和矢量压缩、定义颜色空间、嵌入字体等的参数。有关代码示例,请参阅 ConvertWebMapToArcGISProject 中的示例 2。exportToPDF 方法还包含在输出 PDF 文件中嵌入 PDF 图层、要素属性和地理配准信息的选项。然后可以在 PDF 查看器(如 Adobe Reader)中访问这些要素。有关代码示例,请参阅 ConvertWebMapToArcGISProject 中的示例 3。
注:

ArcGIS Server 还包括名为 PrintingTools 的地理处理服务。PrintingTools 服务可用于 Web 应用程序中,生成高制图质量的可打印图像。有关 PrintingTools 服务的详细信息,请参阅在 web 应用程序中打印

Python 脚本的输出文件(例如,.pdf.png 等)可以是基于可选 template_pagx 参数的布局,且可以包含页面布局整饰要素(例如,标题文本、图例、比例尺、总览图框、格网、经纬网、表格、图表等)。输出也可以是 MapView,这将不包括任何页面布局整饰要素。

将脚本共享为 web 工具

如果您已经获取了一个用于帮助地图打印的 Python 脚本后,则可以在脚本工具中封装该脚本。然后,您可以将脚本工具发布为一个 web 工具ArcGIS API for JavaScriptArcGIS Web AppBuilder 分别具有一个“打印”任务和一个“打印”微件,可供您在 web GIS 应用程序中使用。“打印”任务和“打印”微件均具有 URL 属性,该属性指向所创建的 web 工具的 REST URL。

脚本工具参数

ArcGIS API for JavaScriptArcGIS Web AppBuilder 中的 web 工具中使用 ConvertWebMapToArcGISProject 时,脚本工具的参数名称必须与下表所述的“打印”任务或“打印”微件参数匹配:

脚本工具参数名称数据类型类型说明
Web_Map_as_JSON

字符串

必需项

当地图在 web 应用程序中显示时,待导出的地图状态的 JavaScript Object Notation (JSON) 表示 JSON 是 web 地图的格式。包含 web 地图的完整状态(例如,图层、坐标系、范围、比例等)。ArcGIS API for JavaScriptArcGIS Web AppBuilder 允许您从 web 应用程序获取此 JSON 字符串。

Output_File

文件

派生项

输出文件的名称。文件扩展名取决于 Format 参数。

Format

字符串

可选项

传送打印用地图影像时所使用的格式。接受以下字符串:PDF、PNG、PNG8、PNG32、JPG、GIF、EPS、SVG 和 SVGZ。

Layout_Template

字符串

可选项

可以是列表中某个模板的名称,也可以是关键字 MAP_ONLY。选择 MAP_ONLY 或传递空字符串时,输出地图不会包含任何页面布局的整饰要素(例如标题、图例、比例尺等)。

以下屏幕截图显示了示例脚本工具的参数:

示例脚本工具参数

提示:

使用 ArcGIS API for JavaScript 时,还可以添加任意数量的附加用户定义参数。将额外的参数传递到自定义“打印”任务这一功能非常有用,它允许您从 web 应用程序收集任意数量的额外参数,并将其传递到 Python 脚本中。例如,您的 web 应用程序可能具有一个控件,允许用户选择是否在输出 PDF 中嵌入地理配准信息。有关从 web 应用程序收集额外参数的示例,请参阅 ConvertWebMapToArcGISProject 中的示例 3。

有关设置脚本工具参数的详细信息,请参阅设置脚本工具参数

了解 web 地图 JSON

当您分别使用 ArcGIS API for JavaScriptArcGIS Web AppBuilder“打印”任务或“打印”微件时,无需创建 web 地图 JSON;因为 API 会为您解决这个问题。但是,只有在 ArcGIS Pro 中本地运行脚本之后,才能在 web API 中发布并使用该脚本。本地运行脚本时,可以使用任何有效的 web 地图 JSON 字符串。要成功运行脚本,可能需要与 Web 应用程序返回的字符串类似的 JSON 字符串。请参阅 ExportWebMap 规范来了解对此文本进行格式化的方式。以下是一个示例字符串:

{
    "layoutOptions": {
        "titleText": "Simple WebMap JSON example"
    },
    "operationalLayers": [
        {
            "url": "http://maps1.arcgisonline.com/ArcGIS/rest/services/USA_Federal_Lands/MapServer",
            "visibility": true
        }
    ],
    "exportOptions": {
        "outputSize": [
            1500,
            1500
        ]
    },
    "mapOptions": {
        "extent": {
            "xmin": -13077000,
            "ymin": 4031000,
            "xmax": -13023000,
            "ymax": 4053000
        }
    },
    "version": "1.4"
}
提示:

运行脚本工具时,JSON 字符串可复制并粘贴到 Web_Map_as_JSON 输入参数中。但是,必须移除字符串中的换行符,该字符串才能成为有效输入。以下是一个移除了换行符的示例 JSON 字符串:

{"layoutOptions": {"titleText": "Simple WebMap JSON example"},"operationalLayers": [{"url": "http://maps1.arcgisonline.com/ArcGIS/rest/services/USA_Federal_Lands/MapServer","visibility": true}],"exportOptions": {"outputSize": [1500,1500]},"mapOptions": {"extent": {"xmin": -13077000,"ymin": 4031000,"xmax": -13023000,"ymax": 4053000}},"version": "1.4"}
提示:

或者,可在发布前以通过 ArcGIS Pro 运行时允许空 Web_Map_as_JSON 输入的方式来创作 Python 脚本。可通过多种方法实现此目标。以下代码示例演示了其中一种方法: 将脚本共享为 web 工具后,可通过 web 应用程序中的有效 JSON 对其进行进一步测试。

# If script is executed from within ArcGIS Pro, and WebMap_as_JSON is blank, then don't fail prodName = arcpy.GetInstallInfo()['ProductName'] if (WebMap_as_JSON == '#'):
	if (prodName == 'ArcGISPro'):
		exit()
提示:

如前所述,从 web 应用程序返回的 web 地图 JSON 包含 web 地图的完整状态。web 地图 JSON 中的 layoutOptions 对象保证了进一步讨论,因为它会自动更新可在 template_pagx 中过渡的布局元素。例如,如果 JSON 具有 titleText 设置,且 template_pagx 具有包含标题属性的布局元数据动态文本元素,则模板页面布局中的动态文本将使用 titleText 的值进行更新。有关详细信息,请参阅 ExportWebMap 规范的 layoutOptions 部分。

布局模板

ArcGIS Pro 允许您创建可以另存为布局文件 (.pagx) 的丰富制图布局,从而将其用作 ConvertWebMapToArcGISProject 中的可选 template_pagx 参数。页面布局可以包含地图整饰要素(例如,标题文本、图例、比例尺、总览图框、格网、经纬网、表格、图表等)。地图整饰要素可以是静态的也可以是动态的。这意味着他们可以选择对地图中的变化做出响应。例如,格网和经纬网可以根据比例自动更改,或者,图例项只能显示当前范围内的要素。有关详细信息,请参阅 ArcGIS Pro 中的布局创建布局教程。

当您在 web 工具中封装使用 ConvertWebMapToArcGISProject 的 Python 脚本时,必须确保 ArcGIS Server 可以访问 web 应用程序中使用的布局模板和数据。最佳做法是使用在 ArcGIS Server 中注册的文件夹。有关注册数据的详细信息,请参阅关于将数据注册到 ArcGIS Server

除了创建自己的布局模板之外,您还可以使用软件附带的模板。这些模板位于 <installation_directory>\Templates\ExportWebMapTemplates。这些模板包含图例、当前日期动态文本、比例尺和比例文本等地图整饰元素。这些模板是一个很好的起点。但是请记住,它们是安装目录的一部分,因此,如果卸载或重新安装软件,将会移除它们。可以将这些模板手动复制到 ArcGIS Server 可访问并可进一步修改的位置(如有需要)。

在某些情况下,Web 应用程序中具有单独用于获取有关可用布局模板的信息的任务,可能具有益处。例如,您可能希望知道 web 应用程序的最终用户是否选择了布局元数据动态文本元素包含标题属性的布局模板,以便提示用户输入自己的自定义标题。为实现此目的,PrintingTools 服务具有一个名为“获取布局模板信息”的任务,用于以 JSON 格式返回布局模板的内容。有关获取布局模板信息任务的详细信息,请参阅在 web 应用程序中打印

客户端图形支持

默认情况下,Web 应用程序的注释叠加或客户端图像将存储在内存工作空间中。内存工作空间是临时性的,将在关闭应用程序时被删除。要创建包含注释叠加的输出工程的永久副本,请先指定 notes_gdb,然后使用 ArcGISProject 类的 saveACopy 方法。

使用 updateLayerFromJSON 函数

如果 Web 应用程序使用动态图层,则 Layer 类中的 updateLayerFromJSON 函数可用于使用 Web 地图 JSON 中动态图层的图层定义更新布局模板中过渡矢量图层的属性(例如,符号系统)。如果 Web 应用程序允许更改动态图层的符号系统并且您希望将服务图层替换为过渡的矢量数据,但仍保留 Web 应用程序中更新的符号系统,则上述操作将十分有用。有关使用 updateLayerFromJSON 的示例,请参阅 ConvertWebMapToArcGISProject 中的示例 4。

打印 ArcGIS Server 中包含非基于令牌的安全服务的地图

ImportCredentials 函数可用于从 GIS Server 连接文件访问 ArcGIS Server 非基于令牌的安全服务。凭据可以存储在于 ArcGIS Pro 中创建的连接文件内。有关创建连接文件的详细信息,请参阅连接到 GIS 服务器。在 web 地图打印脚本中,在 ConvertWebMapToArcGISProject 之前使用 ImportCredentials。支持的连接文件类型如下:

  • ArcGIS Server 连接文件 (.ags)
  • WMS 服务器连接文件 (.wms)
  • WMTS 服务器连接文件 (.wmts)

可在使用完 arcpy.ClearCredentials 后清除凭据。

有关代码示例,请参阅 ConvertWebMapToArcGISProject 中的示例 8。

有关详细信息,请参阅打印包含受保护服务的地图

警告:

建议您在未全面了解安全性影响的情况下,不要将凭据嵌入到自定义打印服务中。如果您选择发布自己的服务以使用嵌入凭据进行打印,则建议您应用 ArcGIS Server 安全性规则来限制能够访问该服务的用户。这将阻止匿名用户生成可显示受保护服务的可打印地图图像。有关安全性设置的详细信息,请参阅修改服务或文件夹的权限

在 web 应用程序中使用自定义 Python 脚本的示例

示例1:在 ArcGIS Web AppBuilder 中使用自定义 Python 脚本

本示例显示了如何结合使用 Python 和 ArcGIS Web AppBuilder 可创建用于 web 地图打印的 web GIS 应用程序。最终用户将能够导航至感兴趣区域,在布局模板列表中进行选择(例如,不同的页面大小),以及单击打印按钮。输出将为打印机专用 PDF 或 PNG 文档。如果最终用户选择 PDF 输出,则该文件将包含服务图层的矢量数据。

在本 Python 脚本中,引用了过渡布局模板(.pagx 文件),其中包含所有可能服务图层的矢量等效表示。执行 ConvertWebMapToArcGISProject 函数后,脚本循环遍历输出地图中的所有图层,并从 Web 地图 JSON 中移除所有服务图层,只留下过渡的矢量图层。随后将布局导出为 PDF 或 PNG 文件。

import arcpy
import os
import uuid

# The template location in the server data store
templatePath = '//MyServer/MyDataStore/Templates'

# Input WebMap JSON
Web_Map_as_JSON = arcpy.GetParameterAsText(0)

# Format for output
Format = arcpy.GetParameterAsText(1)

# Input layout template
Layout_Template = arcpy.GetParameterAsText(2)

# Get the requested layout template pagx file
templatePagx = os.path.join(templatePath, Layout_Template + '.pagx')
   
# Convert the WebMap to an ArcGISProject
result = arcpy.mp.ConvertWebMapToArcGISProject(Web_Map_as_JSON, templatePagx, "Layers Map Frame")
aprx = result.ArcGISProject
layout = aprx.listLayouts()[0]

# Reference the map that contains the webmap
m = aprx.listMaps('Web Map')[0]

# Remove the service layer
# This will just leave the vector layers from the template
for lyr in m.listLayers():
    if lyr.isWebLayer:
        m.removeLayer(lyr)
        
# Use the uuid module to generate a GUID as part of the output name
# This will ensure a unique output name
output = 'WebMap_{}.{}'.format(str(uuid.uuid1()), Format)
Output_File = os.path.join(arcpy.env.scratchFolder, output)

# Export the WebMap
if Format.lower() == 'pdf':
    layout.exportToPDF(Output_File) 
elif Format.lower() == 'png':
    layout.exportToPNG(Output_File)

# Set the output parameter to be the output file of the server job
arcpy.SetParameterAsText(3, Output_File) 

# Clean up
del layout, aprx, result

将 Python 脚本工具共享为 web 工具后,ArcGIS Web AppBuilder 中的“打印”微件将被配置为使用 web 工具的 REST URL。以下屏幕截图显示了“打印”微件的配置对话框:

“打印”微件的配置对话框。
提示:

在以上屏幕截图中,将根据 web 工具中的 FormatLayout_Template 参数自动填充默认格式默认布局的值。

示例2:在 ArcGIS API for JavaScript 中使用自定义 Python 脚本

本示例显示了如何结合使用 Python 和 ArcGIS API for JavaScript 可创建用于 web 地图打印的 web GIS 应用程序。最终用户将能够在 web 应用程序中执行以下操作:

  • 导航至感兴趣区域。
  • 选择过渡布局模板。
  • 选择输出格式。
  • 选择是否通过将额外参数从 web 应用程序传递到“打印”任务来将地理配准信息导出为输出 PDF 文件。
  • 将地图导出为包含服务图层矢量输出的方便打印的格式。

在本 Python 脚本中,引用了过渡布局模板(.pagx 文件),其中包含所有可能服务图层的矢量等效表示。执行 ConvertWebMapToArcGISProject 函数后,脚本循环遍历输出地图中的所有图层,并从 Web 地图 JSON 中移除所有服务图层,只留下过渡的矢量图层。随后将输出布局导出为 PDF 或 PNG 文件。本示例还显示了如何将“打印”任务支持的标准参数以外的额外参数 (Georef_info) 从 web 应用程序传递到 Python 脚本。

import arcpy
import os
import uuid

# The template location in the server data store
templatePath = '//MyServer/MyDataStore/Templates'

# Input WebMap JSON
Web_Map_as_JSON = arcpy.GetParameterAsText(0)

# Format for output
Format = arcpy.GetParameterAsText(1)

# Input layout template
Layout_Template = arcpy.GetParameterAsText(2)
    
# Extra parameter - georef_info
Georef_info = arcpy.GetParameterAsText(3)

# Convert Georef_info string to boolean
if Georef_info.lower() == 'false': 
    Georef_info_bol = False
elif Georef_info.lower() == 'true': 
    Georef_info_bol = True
else: Georef_info_bol = True

# Get the requested layout template pagx file
templatePagx = os.path.join(templatePath, Layout_Template + '.pagx')
   
# Convert the WebMap to an ArcGISProject
result = arcpy.mp.ConvertWebMapToArcGISProject(Web_Map_as_JSON, templatePagx, "Layers Map Frame")
aprx = result.ArcGISProject
layout = aprx.listLayouts()[0]

# Reference the map that contains the webmap
m = aprx.listMaps('Web Map')[0]

# Remove the service layer
# This will just leave the vector layers from the template
for lyr in m.listLayers():
    if lyr.isWebLayer:
        m.removeLayer(lyr)
        
# Use the uuid module to generate a GUID as part of the output name
# This will ensure a unique output name
output = 'WebMap_{}.{}'.format(str(uuid.uuid1()), Format)
Output_File = os.path.join(arcpy.env.scratchFolder, output)

# Export the WebMap - use Georef_info_bol to control georeferencing
if Format.lower() == 'pdf':
    layout.exportToPDF(Output_File, georef_info=Georef_info_bol) 
elif Format.lower() == 'png':
    layout.exportToPNG(Output_File, world_file=Georef_info_bol)

# Set the output parameter to be the output file of the server job
arcpy.SetParameterAsText(4, Output_File) 

# Clean up
del layout, aprx, result

将 Python 脚本工具共享为 web 工具。在以下脚本中,ArcGIS API for JavaScript 中的“打印”任务被配置为使用 web 工具的 REST URL:

提示:

该脚本在此行引用了 web 工具的 REST URL:

var printServiceUrl = "https://MyServer:6443/arcgis/rest/services/MyPrintService/GPServer/MyPrintService";

整个脚本如下所示:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
  <title>Austin Print WebApp</title>

  <link rel="stylesheet" href="https://js.arcgis.com/4.3/esri/css/main.css">
  <script src="https://js.arcgis.com/4.3/"></script>

  <style>
    html,
    body,
    #viewDiv {
      padding: 0;
      margin: 0;
      height: 100%;
      width: 100%;
    }
    
    #layerToggle {
      top: 20px;
      right: 20px;
      position: absolute;
      z-index: 99;
      background-color: white;
      border-radius: 8px;
      padding: 10px;
      opacity: 1;
    }
  </style>

  <script>
    require([
        "esri/Map",
        "esri/views/MapView",
        "esri/layers/TileLayer",
        "esri/tasks/PrintTask",
        "esri/tasks/support/PrintParameters",
        "esri/tasks/support/PrintTemplate",
        "dojo/dom",
        "dojo/on",
        "dojo/domReady!"
      ],
      function(
        Map, MapView, TileLayer, PrintTask, PrintParameters, PrintTemplate, dom, on
      ) {

        /*****************************************************************
         * Create a TileLayer instance. 
         *****************************************************************/
        var austinLyr = new TileLayer({
          url: "http://MyServer:6080/arcgis/rest/services/MyMapService/MapServer",
          id: "austin",
          opacity: 0.9
        });

        /*****************************************************************
         * Layers may be added to the map in the map's constructor
         *****************************************************************/
        var map = new Map({
          basemap: "oceans",
          layers: [austinLyr]
        });

        var view = new MapView({
          container: "viewDiv",
          map: map
        });
        
        /*****************************************************************
         * Go to extent of Austin Layer
         *****************************************************************/
        view.then(function() {
          austinLyr.then(function() {
            view.goTo(austinLyr.fullExtent);
          });
        });

        var printServiceUrl = "https://MyServer:6443/arcgis/rest/services/MyPrintService/GPServer/MyPrintService";
        
        var printMode = "async";
        
        var printTask = new PrintTask({
   			url: printServiceUrl,
   			mode: printMode
			});
		
	function myPrint() {
		// input layout template
		var layout = dom.byId("layout");
	        var index = layout.selectedIndex;
	        var selectedValue_layout = layout.options[index].value;
	        
	        // format for output
	        var format = dom.byId("format");
	        var index = format.selectedIndex;
	        var selectedValue_format = format.options[index].value;
	        
	        // Extra parameter: Georeferencing info boolean
	        var georef_info = dom.byId("georef_info");
	        var index = georef_info.selectedIndex;
	        var selectedValue_georef_info = georef_info.options[index].value;
	        
	        var template = new PrintTemplate({
			 format: selectedValue_format,
			 layout: selectedValue_layout, 
			});
	
		var params = new PrintParameters({
			 view: view,
			 template: template
			});
			
		params.extraParameters = {
          		Georef_info : selectedValue_georef_info
        		};
	        
		printTask.execute(params).then(printResult, printError);
			}
		
		function printResult(result) {
        	window.open(result.url);
     		}	
     		
     	function printError(result) {
        	alert('Error printing.')
     		}
     		
     	// Call myPrint() each time the button is clicked    
      	on(dom.byId("doBtn"), "click", myPrint);

      });
  </script>
</head>

<body>
  <div id="viewDiv"></div>
  <span id="layerToggle">
    Layout Template:
    <select id="layout" >
      <OPTION value="Austin26x28">Austin26x28</OPTION>
      <OPTION value="Austin24x36">Austin24x36</OPTION>
    </select>
    &nbsp;&nbsp;Format:
    <select id="format">
      <OPTION value="PDF">PDF</OPTION>
      <OPTION value="PNG">PNG</OPTION>
    </select>
	
    &nbsp;&nbsp;Include Georef info?
    <select id="georef_info">
      <OPTION value="True">True</OPTION>
      <OPTION value="False">False</OPTION>
    </select>
    <button id="doBtn">Print Map</button>
  </span>
</body>

</html>