八、制图模块【ArcGIS Python系列】

2023-11-24 16:24:56 浏览数 (1)

Arcpy.mp 主要是用于操作现有工程 (.aprx) 和图层文件 (.lyrx) 的内容,使用 arcpy.mp 自动执行重复性任务,例如修改地图属性、添加图层、应用符号系统和导出布局。可以自动化工程的内容,甚至无需打开应用程序。

以下简单示例显示了如何使用 arcpy.mp 通过仅仅四行代码引用工程中现有布局并将其导出至 PDF 文档。

代码语言:javascript复制
import arcpy
aprx = arcpy.mp.ArcGISProject(r"C:ProjectsYosemiteNPYosemite.aprx") # 将引用磁盘上的 ArcGIS Pro 工程
lyt = aprx.listLayouts("Main Attractions*")[0] # 查找名称以 Main Attractions 一词开头的第一个布局
lyt.exportToPDF(r"C:ProjectYosemiteNPOutputYosemite.pdf", resolution=300) # 将布局导出至输出分辨率为 300 的 PDF

ArcGIS Desktop 10.x中ArcPy中的模块被称为 arcpy.mapping ,但ArcGIS Pro的map功能发生了重大更改!!!因此与ArcGIS Pro不兼容,官方提供了迁移指南:从 arcpy.mapping 迁移至 ArcGIS Pro。

一、理解和引用工程(Projects)

1.ArcGIS Pro中的工程

ArcGIS Pro 将您的 GIS 项目组织到工程中。 工程可以包含地图、场景、布局、报表、图表以及空间和非空间数据的其他制图表达。 工程还包含与诸如系统文件夹、数据库、工具箱、服务器、样式和活动门户等数据资源的连接。在你进入程序时,你创建的就是工程。

ArcGIS Pro中的工程.aprx文件的形式存储在磁盘上,例如C:MappingStudy.aprx。ArcPy制图模块允许您引用和操作.aprx文件以及包含各个图层特性的图层文件(.lyrx)

要引用工程有两种方法,一是指定磁盘上现有的.aprx文件,二是在程序中使用时,直接引用当前ArcGIS PRO窗口中的工程,所以不适用于独立的IDE中的脚本。引用工程的arcpy.mp.ArcGISProject() 函数实现,实际过程中推荐使用序内运行制图模块脚本,独立脚本有或多或少的bug,比如在独立脚本中不能实时控制地图图层的可见性。我们通过下面代码引用工程:

代码语言:javascript复制
# 方式一
aprx = arcpy.mp.ArcGISProject("C:/Mapping/Study.aprx") 
# 方式二 适用于程序内运行的脚本 
aprx = arcpy.mp.ArcGISProject("CURRENT")

从中可以看到arcpy中的工程对象aprx为mp模块的ArcGISProject类,aprx继承了类的属性和方法,然后我们可以读取和修改工程的属性。后续所说的地图和布局都是对相应类的实例化。整个制图模块的结构如下图所示:

当在python中引用ArcGISProject对象是,工程文件会被锁定,可以用del aprx来释放此对象。脚本运行完之后,python会自动删除对对象的引用,所以del语句不是必须的。

2.工程的属性和方法

描述性属性: activeMap (与聚焦视图关联的映射)、 dateSaved (上次保存工程的日期)、 documentVersion (上次保存文档时的版本)和 filePath (完整的工程路径和文件名)、defaultGeodatabase (工程的默认地理数据库位置)、 defaultToolbox (工程的默认工具箱)和 homeFolder (工程的主文件夹位置)

常用的描述性属性:

  • defaultGeodatabase(可读写)

工程的默认地理数据库位置。 字符串必须包含地理数据库的完整路径和文件名。

  • StringdefaultToolbox(可读写)

工程的默认工具箱位置。 字符串必须包含工具箱的完整路径和文件名。

如何导入ArcMap的地图文档(.mxd文件)?

使用 importDocument() 方法。

此方法的语法为ArcGISProject.importDocument(document_path, {include_layout}, {reuse_existing_maps})

下面的脚本演示了如何将文档导入到现有 ArcGIS Pro 工程。 同时还设置了一些默认工程设置并将结果保存到新文件。

代码语言:javascript复制
import arcpy
aprx = arcpy.mp.ArcGISProject(r"C:Projectsblank.aprx")
aprx.importDocument(r"C:ProjectsYosemiteNPDocumentsYosemite.mxd")
aprx.importDocument(r"C:ProjectsYosemiteNPDocumentsYosemite_ScenicViews.3dd")
aprx.defaultGeodatabase = r"C:ProjectsYosemiteNPData_VectorYosemiteData.gdb"
aprx.defaultToolbox = r"C:ProjectsYosemiteNPAnalysisAnalysisTools.tbx"
aprx.saveACopy(r"C:ProjectsYosemiteNPYosemite.aprx")

ArcGIS Desktop 10.x使用单独的应用程序来处理这些文档,即ArcMap、ArcGlobe和ArcScene。这些应用程序的功能已集成到ArcGIS Pro中。还可以导入地图文件(.mapx)、布局文件(.pagx)和报告文件(.rptx)的内容。.mxd文件中的每个数据帧都将成为.aprx文件中的地图。此地图包括所有图层及其符号系统。.mxd文件中的唯一布局将成为.aprx文件中的布局。此布局包含所有布局元素,包括比例尺、图例等。

二、理解和使用地图(map)

Map 对象是参考和管理 ArcGIS Pro 工程中的图层和表的主要对象。一个工程(project)包含一个或多个地图,每个地图通常包含一个或多个图层和表。

图示即为地图,一个工程可以有多个地图,地图标签的名字即为map的name属性,图示的name属性为默认的“地图”。

使用ArcGISProject 类的 listMaps() 方法访问映射:

代码语言:javascript复制
import arcpy
aprx = arcpy.mp.ArcGISProject("C:/Mapping/Demo.aprx")
maps = aprx.listMaps() 
# 可以通过wild_card属性过滤,不区分大小写
# 使用maps = aprx.listMaps(“P*”)来选择P开头的地图

for m in maps:    
 print(m.name)
del aprx
1.地图对象的常用属性

属性

说明

name(可读写)

用于在 Map 对象出现在内容列表中时获取或设置其名称,同时还用于获取或设置布局内的实际元素名称。

spatialReference(可读写)

用于获取或设置与地图相关联的 SpatialReference。

2.地图对象的常用方法

方法

说明

addBasemap (basemap_name)

addBasemap 可用于在地图内添加或替换底图图层。

addDataFromPath (data_path, {web_service_type}, {custom_parameters})

addDataFromPath 允许通过提供本地路径或 URL 向工程 (.aprx) 中的地图添加图层。

addLayer (add_layer_or_layerfile, {add_position})

用于使用基本放置选项向工程 (.aprx) 内的地图添加 Layer 或 LayerFile。

insertLayer (reference_layer, insert_layer_or_layerfile, {insert_position})

用于通过指定特定位置向工程 (.aprx) 内的地图添加 Layer 或 LayerFile。

clearSelection ()

清除地图中所有图层和表的选择。

listLayers ({wildcard})

返回存在于地图中的 Layer 对象的 Python 列表。

listTables ({wildcard})

返回存在于地图中的 Table 对象的 Python 列表。

3.示例

下面的脚本引用了图层文件并将图层插入地图中已存在的图层上方:

代码语言:javascript复制
import arcpy
aprx = arcpy.mp.ArcGISProject(r"C:ProjectsYosemiteNPYosemite.aprx")
insertLyr = arcpy.mp.LayerFile(r"C:ProjectsYosemiteNPLayerFilesRanger Stations.lyrx")
m = aprx.listMaps("Yosemite National Park")[0]
refLyr = m.listLayers("Points of Interest")[0]
m.insertLayer(refLyr, insertLyr, "BEFORE")
aprx.saveACopy(r"C:ProjectsYosemiteNPYosemite_updated.aprx")

三、理解和使用图层(Layer)

ArcGIS Pro中的地图通常包含一个或多个图层。图层是对数据源(如shapefile、地理数据库要素类或栅格)的引用,用于定义数据在地图上的符号化方式。 arcpy.mp 中的两个类与层一起工作: LayerLayerFile 这两种类型的对象都用于管理图层。

可以使用 Map.listLayers() 方法访问地图中的图层。语法Map.listLayers({wildcard})。方法返回一个 Layer 对象的列表,这些对象具有属性和方法。例如,下面的代码遍历项目中的地图,然后打印每个地图中所有图层的名称:

代码语言:javascript复制
import arcpy
aprx = arcpy.mp.ArcGISProject("C:/Mapping/Demo.aprx")
maps = aprx.listMaps()
for m in maps:    
 print("Map: "   m.name)    
 lyrs = m.listLayers()    
 for lyr in lyrs:        
  print(lyr.name)
del aprx 

Layer 对象有许多属性: namedataSourcebrightnesscontrastshowLabelssymbologytransparencyvisible

以下脚本将清除名为 Yosemite National Park 的地图中的所有图层定义查询并关闭所有图层的标注:

代码语言:javascript复制
import arcpy
aprx = arcpy.mp.ArcGISProject(r"C:ProjectsYosemiteNPYosemite.aprx")
m = aprx.listMaps("Yosemite National Park")[0]
for lyr in m.listLayers():
    if lyr.supports("DEFINITIONQUERY"):
        lyr.definitionQuery = ""
    if lyr.supports("SHOWLABELS"):
        lyr.showLabels = False
aprx.save()
del aprx

拓展:确定图层类型的另一种方法是在尝试将属性应用于某个图层之前,在 Layer 对象上使用 supports() 方法来测试该图层是否支持特定属性。supports() 方法的语法是Layer.supports(<layer_property>),返回布尔值。使用此方法避免脚本出错。

代码语言:javascript复制
if lyr.supports("BRIGHTNESS"):
 lyr.brightness = 10

1.图层对象常用方法

1)给地图添加底图:
代码语言:javascript复制
Map.addBasemap(basemap_name)

basemap_name 参数基于底图库中的显示名称,而不是图层被添加到地图之后的名称。

可以使用 Map.removeLayer() 方法删除底图。

2)给地图添加数据

在ArcGIS Pro中,可以通过从目录窗格中拖动数据集或单击地图选项卡上的添加数据来将数据添加到地图。此功能在ArcPy中使用 Map.addDataFromPath() 方法复制。此方法的语法为

代码语言:javascript复制
Map.addDataFromPath(data_path)

data_path 参数是表示本地路径或URL的字符串。以下代码将要素类添加到现有地图:

代码语言:javascript复制
import arcpy
aprx = arcpy.mp.ArcGISProject("C:/Mapping/Demo.aprx")
m = aprx.listMaps("Parks")[0]
m.addDataFromPath("C:/Mapping/Data.gdb/sidewalks")
aprx.save()
del aprx

通过创建图层对象来删除添加的数据:

代码语言:javascript复制
import arcpy
aprx = arcpy.mp.ArcGISProject("C:/Mapping/Demo.aprx")
m = aprx.listMaps("Parks")[0]
lyr = m.listLayers("historical_landmarks")[0]
m.removeLayer(lyr)
aprx.save()
del aprx

除了通过引用磁盘上的数据向地图添加新图层外,还可以通过引用 LayerLayerFile 对象添加图层。您可以使用 Map.addLayer()Map.insertLayer() 方法添加图层。这些方法以类似的方式工作,但后者提供了对图层放置的更细粒度的控制。这两个方法中的第一个方法的语法为Map.addLayer(add_layer_or_layerfile, {add_position})

此语法与 addDataFromPath() 方法类似,但.lyrx文件不仅包含对源数据的引用,还包含由图层文件的作者创建的自定义符号系统。可以使用保存到图层文件地理处理工具创建.lyrx文件。创建图层文件通常用于在项目之间和用户之间共享符号系统。

Map.addLayer() 方法也可以使用 Layer 对象而不是 LayerFile 对象。此方法的典型应用是引用一个地图中的图层,然后将其添加到同一项目中的另一个地图中。以下代码引用公园地图中名为city_parks的图层,然后将此图层添加到设施地图中:

代码语言:javascript复制
import arcpy
aprx = arcpy.mp.ArcGISProject("C:/Mapping/Demo.aprx")
m = aprx.listMaps("Parks")[0]
lyr = m.listLayers("city_parks")[0]
m = aprx.listMaps("Facilities")[0]
map.addLayer(lyr, "BOTTOM")
aprx.save()
del aprx
3)使用图层符号系统

如果你需要可视化调试更加美观的符号系统,还是选择程序中操作,以下仅适用于符号系统的样式最终确定之后,利用符号系统的脚本实现自动化流程。

使用 Layer 对象时,将保留其符号系统。例如,当引用一个地图中的图层并使用 Map.insertLayer() 方法将图层添加到另一个地图时,符号系统是相同的。

此外,还可以通过 Layer 对象的属性(包括亮度、对比度和透明度)修改符号系统的某些方面。这些属性是可读写的。然而,符号系统的许多其他方面需要更细粒度的控制。

四、理解和使用符号系统(Symbology)

使用符号系统是通过 Symbology 类完成的,可以通过 Layer 对象的 symbology 属性访问该类。在典型的工作流中,可以引用图层的 symbology 属性,对 Symbology 对象进行更改,然后将这些更改应用于图层。

Symbology 类具有两个属性,用于定义图层的符号化方式: colorizer (用于栅格层)和 renderer (用于要素图层)。这些属性返回用于符号化图层的着色器渲染器。还可以使用 updateColorizer() (用于栅格图层)和 updateRenderer() (用于要素图层)方法更改着色器或渲染器的类型。


下面的示例代码迭代地图中的图层。

代码语言:javascript复制
import arcpy
aprx = arcpy.mp.ArcGISProject("C:/Mapping/Symbology.aprx")
m = aprx.listMaps("Plan")[0]
lyrs = m.listLayers()
for lyr in lyrs:
  # 使用 Layer 对象的 symbology 属性创建一个 `Symbology` 对象
 sym = lyr.symbology
 
 # 检查图层的类型
 if lyr.isFeatureLayer:
   if hasattr(sym, "renderer"): # 图层是否支持着色器
     print(lyr.name   ": "   sym.renderer.type)
  if lyr.isRasterLayer:
   if hasattr(sym, "colorizer"): # 图层是否支持渲染器
     print(lyr.name   ": "   sym.colorizer.type)

以下代码使用RGB(红色、绿色、蓝色)值更新多段线的颜色:

代码语言:javascript复制
import arcpy
aprx = arcpy.mp.ArcGISProject("C:/Mapping/Symbology.aprx")
m = aprx.listMaps("Plan")[0]
lyr = m.listLayers("trails")[0]
sym = lyr.symbology
red = {"RGB": [255, 0, 0, 100]}
 if lyr.isFeatureLayer and hasattr(sym, "renderer"):
    
    # 将RGB值分配给 Symbol 对象的 color 属性
  sym.renderer.symbol.color = red
    
    # 将新符号系统应用于lyr图层的符号系统属性
  lyr.symbology = sym
    
aprx.save()
del aprx

可以使用符号库中预定义的样式名称:

image-20230811121409439

使用 Symbol 类的 applySymbolFromGallery() 方法更新符号系统:sym.renderer.symbol.applySymbolFromGallery("Dashed 4:4")

如果符号同名使用该方法的可选参数:

image-20230811121607536

代码语言:javascript复制
sym.renderer.symbol.applySymbolFromGallery("Hospital", 1) # 选择第二个医院符号(从0开始)

1.应用分级颜色符号系统

使用 Symbol.updateRenderer() 方法来更改图层的渲染器,

下面的示例使用空置房屋的原始计数(在名为VACANT的字段中),并通过所有房屋单元的原始计数(在名为HSE_UNITS的字段中)对该计数进行归一化,得到一个分数。使用五个类别应用等间隔分类:

sym.renderer.classificationField = "VACANT" sym.renderer.normalizationField = "HSE_UNITS" sym.renderer.classificationMethod = "EqualInterval" sym.renderer.breakCount = 5

image-20230811122419694

代码语言:javascript复制
sym.renderer.colorRamp = aprx.listColorRamps("Yellow-Orange-Red                                                  (5 Classes)")[0]

最后,使用 lyr.symbology = sym 将新符号系统应用于图层,并保存项目以使更改生效:

代码语言:javascript复制
lyr.symbology = symaprx.saveACopy("Housing_Choropleth.aprx")del aprx

参考官方文档写一个案例。

2.将符号系统应用于栅格图层

可以对土地利用图进行映射

五、理解和使用布局(layout)

Layout 对象将引用 ArcGIS Pro 工程 (.aprx) 中的单个页面布局。可用于访问常见属性(如页面大小)和多个不同的导出方法。Layout 对象提供对布局的名称、页面大小和页面单位等属性的访问。我们使用ArcGISProject.listLayouts({wildcard})来引用此布局类。该方法返回一个 Layout 对象的列表,每个对象引用一个单页布局,该Layout 对象对象一般将其命令为lyt

Layout 对象上的文字、文本框称之为布局元素,即Elements。常见的地图元素包括一个或多个地图框(每个地图框都含有一组有序的地图图层)、比例尺、指北针、地图标题、描述性文本和图例。为提供地理参考,可以添加格网或经纬网。地图框、文本等页面布局元素可以用过此函数引用:Layout.listElements ({element_type}, {wildcard})。之后可以对返回的元素对象进行例如名称、高度、宽度、位置、旋转、可见性进行修改。

布局元素概述

1.使用页面上地图、地图框、相机

首先得理解其概念:

地图框:存在于布局中,用来限制地图图层的展示边界,就像打开了另一个地图视图一样。地图框可以指向工程中的任何地图或场景,也可以完全不指向任何地图。 创建地图框后,可以随时更新其指向的地图。

camera 属性:此属性允许访问 Camera 对象,该对象控制地图框中显示的数据的位置和查看位置。Camera 对象控制视图的XYZ值。对于二维贴图,XY值基于贴图框的中心,而Z值不受支持。对于三维贴图,XYZ值表示相机相对于视图的位置。 Camera 对象还包括一个比例属性,该属性仅适用于2D地图,因为其平面视图。

常见的任务是使多个地图帧的范围相同,这些方法可用于此任务。下面的代码示例采用一个地图框的范围,并将相同的范围应用于同一布局中的第二个地图框:

代码语言:javascript复制
import arcpy
aprx = arcpy.mp.ArcGISProject("C:/Mapping/Demo.aprx")
lyt = aprx.listLayouts("Parks")[0]
mf1 = lyt.listElements("MAPFRAME_ELEMENT", "Park A")[0]
ext = mf1.camera.getExtent() # 由左下角和右上角的坐标

mf2 = lyt.listElements("MAPFRAME_ELEMENT", "Park B")[0]
mf2.camera.setExtent(ext)
aprx.save()
del aprx

2.导出和打印布局

以使用与 Layout 对象关联的导出方法来导出布局。每种文件格式都有不同的方法,如下所示,常用的exportToJPEG()exportToPNG()exportToPDF()exportToTIFF()

推荐用PNG而不是JPG:JPEG文件是网络上流行的格式,因为文件大小比许多其他格式都小,但该算法使用有损压缩,这意味着原始图像中的一些数据会丢失,绘图和文本等元素可能会变得模糊。

也推荐使用PDF格式:它可以跨不同平台查看和打印。PDF文件也可以在其他应用程序中编辑,并保留布局中的大部分信息,包括地理配准信息、注释和标签。


拓展: PDFDocument

由于PDF文档被广泛用于导出布局,因此 arcpy.mp 包含了一个 PDFDocument 类来管理PDF文件,包括合并多个文件删除页面管理文档安全设置PDFDocument 类只有一个属性: pageCount ,它是页数的整数。 PDFDocument 类有六个方法: appendPagesdeletePagesinsertPagessaveAndCloseupdateDocProperties(和 updateDocSecurity

下面的代码将创建新 PDF 文档、追加三个独立 PDF 文档的内容并保存生成的 PDF 文件,如下所示:

代码语言:javascript复制
import arcpy, os

# 删除已经存在的pdf
pdfPath = r"C:ProjectsYosemiteNPAttractionsMapBook.pdf"
if os.path.exists(pdfPath):
    os.remove(pdfPath)

# 创建新 `PDFDocument` 对象
pdfDoc = arcpy.mp.PDFDocumentCreate(pdfPath)

# 将现有pdf追加到pdf对象最后一页
pdfDoc.appendPages(r"C:ProjectsYosemiteNPTitle.pdf")
pdfDoc.appendPages(r"C:ProjectsYosemiteNPMapPages.pdf")
pdfDoc.appendPages(r"C:ProjectsYosemiteNPContactInfo.pdf")

# 保存 删除此对象的变量
pdfDoc.saveAndClose()
del pdfDoc

以下脚本将使用 deletePages 然后使用 insertPages 替换现有 PDF 中的四页内容。请注意在当前显示的第 3 页的页面前插入新第 3 页的方法,在最初的第 3 页移除之前,现在的第 3 页其实是第 4 页。在 5-7 页中同样应用了该方法。

代码语言:javascript复制
import arcpy
pdfDoc = arcpy.mp.PDFDocumentOpen(r"C:ProjectsYosemiteNPAttractionsMapBook.pdf") 
pdfDoc.deletePages("3, 5-7") # 删除第3 5 6 7页

# 替换第3页
pdfDoc.insertPages(r"C:ProjectsYosemiteNewPage3.pdf", 3) # 1.第二个参数前添加 2.数字是页码数 从1算起
# 替换第5-7页
pdfDoc.insertPages(r"C:ProjectsYosemiteNewPages5-7.pdf", 5) # 插入第3页之后 页码会更新 此时在第4页后,原第8页前插入第5-7页
pdfDoc.saveAndClose()
del pdfDoc

实操:批量分图层导出地图

我们有一系列地图,要分图层出图,可以使用如下方法:

代码语言:javascript复制
import time, os

def export_to_png(i, lyrs, dest_folder, total_page):
    """打开指定的图层图层并导出为png
    :param i: 图层索引
    :param lyrs: 图层对象
    :param dest_folder: 导出文件夹路径
    :param total_page: 图层数量
    """
    print(f"当前处理第{i}个图层,图层名称是:", lyrs[i].name)
    
    # 设置图层可见性
    
    # 选择等于当前i索引的可见性为真 其余为假
    for j in range(total_page):
        if j == i:
            # 显示j图层
            if lyrs[j].visible == False:
                lyrs[j].visible = True
        else:
            # 关闭j图层
            if lyrs[j].visible == True:
                lyrs[j].visible = False
    
    # 导出为png
    file_name = lyrs[i].name   ".png"
    file_path = os.path.join(dest_folder, file_name)
    lyt.exportToPNG(out_png=file_path,
                   resolution=250
                   )
    print(f"{file_path}保存成功")
    

if __name__ == '__main__':
    start = time.time()
    
    # 定义工作空间
    arcpy.env.workspace = r"~DocumentsPython_GISLessons获取哨兵2遥感地图并计算ndvi" 
       
    # 引用当前项目
    project="current" # 当前项目
    aprx = arcpy.mp.ArcGISProject(project)
    
    # 获取地图
    map_wild="波段合成" # 地图名称
    m = aprx.listMaps(map_wild)[0]
    print("当前地图是 "   m.name)
    
    # 获取图层
    lyrs = m.listLayers()
    
    # 获取图层数量 处理异常情况
    total_page = len(lyrs) # 图层数量
    if total_page!= 0:
        print("图层数量: " , total_page)
    else:
        print("错误:未找到图层") 
    
    # 获取布局
    lyt = aprx.listLayouts()[0]

    # 定义导出文件夹
    dest_folder = os.path.join(arcpy.env.workspace, "output")
    # 创建导出文件夹
    if not os.path.exists(dest_folder):
        os.makedirs(dest_folder)
    
    # 循环导出
    for i in range(total_page):
        # 调用导出函数
        export_to_png(i, lyrs, dest_folder, total_page)
        
    end = time.time()

0 人点赞