使用 arcpy.mapping 更新和修复数据源 (arcpy.mapping)

在许多情况下,您都可能需要修复数据源或重定向数据源至其他位置。然而,如果是在每个相关的地图文档中手动进行更改,则会显得异常麻烦。arcpy.mapping 脚本环境提供了多种方法使得您无需打开地图文档即可自动进行更改。您可针对各个图层逐一更新数据源,也可一次更新同一工作空间中的所有图层。各方法的帮助主题都提供了简要介绍,而此文档的目的在于对这些方法进行概括和比较。

实际上,基本存在两种不同类型的方法用于处理 MapDocumentLayerTableView 类。方法名称会根据可用的类和参数的不同而稍有变化。

这些方法的功能相互间互有重复;因此,可通过多种方法更新图层或表的工作空间或数据集。此文档的目的在于讨论所有方法并针对许多常见情景提供最佳操作建议。

以下是本文档中所使用的一些术语的定义:

用于更新和修复数据源的方法

一般用法说明

  • ListBrokenDataSources() 函数对于确定地图文档或图层文件中损坏的图层或表很有用。
  • 这些方法对所有图层和表进行递归处理。这意味着,还会搜索和更新连接表和关联表(请参阅下面的“已知局限性”部分)。
  • 可以查找和替换整个工作空间路径或其中的一部分。如果仅替换部分路径,则需确保它是唯一字符串。例如,对于驱动器而言,仅查找字母 C 并将其替换为字母 D 的操作结果不如查找 r"C:\" 并替换为 r"D:\" 的操作结果更加明确。
  • 使用图层名称区分数据源并非完全可靠,因为可能存在多个数据源具有相同图层名称的情况。可能需要使用其他图层属性来唯一地区分图层。
  • 不要在工作空间路径中包含地理数据库要素数据集的名称。要素数据集是工作空间的组成部分。例如,如果将要素类从独立要素类移到地理数据库要素数据集中,则地图文档会忽略损坏图层而正常打开。
  • 工作空间路径的参数值中不应包括图层或表的数据集名称;因此,如果图层或表的数据集名称发生更改,则上述许多方法将不可用。LayerTableView 对象的 replaceDataSource 方法即可应对此种情况。
  • 通过 replaceWorkspaces 方法切换工作空间时,数据集名称必须相同。例如,对于名称为 Highways.shp 的 shapefile,只有在文件地理数据库中数据集的名称也为 Highways 时,才可以将其重定向至该文件地理数据库工作空间。如果数据集名称不同,则需要调用 LayerTableView 对象的 replaceDataSource 方法。
    • 如果对 old_workspace_path 参数赋予空字符串 ("") 并对 old_workspace_type 参数赋予 "NONE" 值,则所有数据源都与新的工作空间相匹配。这样,您通过一种方法即可将多个工作空间重定向到一个工作空间。空字符串只能与关键字 NONE 结合使用。

可选参数 validate

  • 此参数允许您在更改或替换图层或表的数据源之前测试新数据源是否存在。
  • 如果 validateTrue 并且新数据源存在且有效,则原始数据源将更新为新数据源;否则将继续指向原始数据源并不做更新。
  • 如果 validateFalse,则新数据源不必有效(即,它可能尚不存在)。这对尚未创建数据时需要更新数据源的情况十分有用。请注意,在上述情况下,数据在关联的地图文档或图层文件中将显示为已损坏。
    • 使用 findAndReplaceWorkspacePathfindAndReplaceWorkspacePaths 方法时,validate 参数应用于 replace_workspace_path 参数。
    • 使用 replaceWorkspacePaths 方法时,validate 参数应用于 new_workspace_pathnew_workspace_type 参数。
    • 使用 replaceDataSource 方法时,validate 参数应用于 dataset_name 参数。

使用 SDE 连接

  • SDE 连接的工作空间路径是 SDE 连接 (.sde) 文件的路径。您可以提供文件的完整路径,如果连接文件位于数据库连接文件夹中,也可以在路径中使用该字符串,例如 find_workspace_path=r"Database Connections\myConnectionFile.sde"
  • 脚本中使用的 SDE 连接文件信息必须与用来将数据添加到地图文档或图层文件的 SDE 连接相同。例如,两个不同的位置可以具有两个相同的 SDE 连接。如果已使用第一个连接文件路径将数据添加到地图中,但在脚本中使用的是第二个连接文件路径,则数据源将不会按预期进行更新。
    • LayerTableView 对象的 dataSource 属性可用于确定地图文档或图层文件中所用的路径。使用此值来帮助确定应该使用哪个 SDE 连接文件路径。如下所示:
      lyr.findAndReplaceWorkspacePath(find_workspace_path=lyr.dataSource, replace_workspace_path=r"C:\Newpath\To\SDE_ConnectionFile.sde")
      
  • 当从 SDE 连接移除密码信息以便使其在地图文档或图层文件中不再保留时,需要设置 validate=False。因为如果试图使用不保留所有正确连接信息的 SDE 连接文件,则显然连接会失败,所以必须执行此操作。设置 validate=False 将强制使用新连接文件,当用户打开包含 SDE 数据的地图文档时系统会提示输入登录信息。

已知局限性

  • 由于逻辑示意图数据集设计中包含隐藏表,因此无法对其更新。
  • 目前不支持直接与 SQL Server Express 数据库建立数据库服务器连接。一种解决办法是,改为创建与 SQL Server Express 数据库的空间数据库连接并加以使用。
  • 与栅格图层有关的连接和关联不会进行更新。
  • SQL 查询不会自动更新。不同的工作空间(例如,个人与文件地理数据库)使用不同的 SQL 语法进行图层定义查询,并且该语法不会自动更新。通过更新图层对象的 definitionQuery 属性,可更正语法。可通过简单的 Python 字符串函数来执行基本的搜索和替换操作。例如,
    • 对于个人地理数据库,字段名称用方括号 ([]) 括起来,而对于文件地理数据库,则使用引号 ("")。
    • 个人地理数据库的通配符为星号 (*) 和问号 (?),而文件地理数据库的通配符则为百分号 (%) 和下划线 (_)。

    • 个人地理数据库中的字符串搜索不区分大小写,但在文件地理数据库中则区分大小写。

    • 个人地理数据库使用 UCASELCASE 转换字符串大小写。文件地理数据库使用 UPPERLOWER

    • 个人地理数据库使用井号 (#) 分隔日期和时间,而在文件地理数据库中它们以单词 date 开头。

  • 图层标注表达式也不会自动进行更新。也需要对它们进行更改来支持相应的 SQL 语法。更新图层的 LabelClass SQLQueryexpression 属性可更正语法。
  • 更新图层的数据源时,地图文档中已保存的选择会被清除(在 ArcMap 中使用图层属性 对话框的设置数据源时也是如此)。目前此问题尚无解决方法,只能手动重新创建地图文档要素选择。

常见情景

基于文件的数据被移至其他文件夹或放到其他网络驱动器中

映射的网络驱动器字母或数据位置的文件夹结构发生了更改,或出现了一些其他情况导致与需要更新的现有数据的连接中断。在这种情况下,仅需将相同数据源重定向到新的文件夹位置或驱动器名称。这种情况几乎适用于包括空间数据库连接文件的所有基于文件的数据结构(例如,shapefile、CAD 数据、个人地理数据库和文件地理数据库以及基于文件的栅格)。

In this scenario, the data was located directly under the C:\Project\Data folder but was moved into a subfolder called Data2. This script updates a single map document.

import arcpy
mxd = arcpy.mapping.MapDocument(r"C:\Project\Project.mxd")
mxd.findAndReplaceWorkspacePaths(r"C:\Project\Data", r"C:\Project\Data2")
mxd.saveACopy(r"C:\Project\Project2.mxd")
del mxd

将本地路径更改为 UNC 路径

在此情况下,用户希望与可访问其驱动器的其他员工共享地图文档,并且希望员工不必将数据复制到本地驱动器上即可成功打开地图文档。

This scenario involves changing a local system path to a UNC path. This script updates all the map documents in a folder.

import arcpy, os
folderPath = r"C:\Project"
for filename in os.listdir(folderPath):
    fullpath = os.path.join(folderPath, filename)
    if os.path.isfile(fullpath):
        basename, extension = os.path.splitext(fullpath)
        if extension.lower() == ".mxd":
            mxd = arcpy.mapping.MapDocument(fullpath)
            mxd.findAndReplaceWorkspacePaths(r"C:\Project\Data", r"\\ComputerName\Project\Data")
            mxd.save()
del mxd

SDE 连接属性发生更改

出现这种情况的原因有很多 - 可能是服务器名称和/或端口号发生更改;连接包括登录凭据,但出于安全考虑需要将其移除;密码被更改;连接由 3 层连接更改为 2 层直连;或者希望将图层指向不同的版本。无论哪种情况,修改空间数据库连接属性时,只需将路径传递到连接文件即可。

In this scenario, a user wants to change the geodatabase version for all map layers. The user creates a new SDE connection file and replaces the original connection file with the new connection file.

import arcpy
mxd = arcpy.mapping.MapDocument(r"C:\Project\Project_default.mxd")
mxd.findAndReplaceWorkspacePaths(r"C:\Project\Connection to Default.sde", 
                                 r"C:\Project\Connection to Version1.sde")
mxd.saveACopy(r"C:\Project\Project_V1.mxd")
del mxd

In this scenario, a user wants to remove the password information saved within a map document. The data sources in the map document came from an SDE connection file where the password information was saved with the connection information. Next, the user created a new SDE connection file to the same database but this time did not save the password information. In the script below, the validate parameter must be set to False for the password information to be successfully removed. After the script is run, a user will need to log in to open the resulting map document.

import arcpy
mxd = arcpy.mapping.MapDocument(r"C:\Project\Project_default.mxd")
mxd.findAndReplaceWorkspacePaths(r"C:\Project\Connection with password info saved.sde", 
                                 r"C:\Project\Connection with no password info saved.sde", False)
mxd.saveACopy(r"C:\Project\Project_NP.mxd")
del mxd

在不同工作空间类型之间迁移数据

某些通常情况下,用户需要将 shapefile 和个人地理数据库等数据迁移到文件地理数据库或从文件地理数据库迁移到企业级 SDE 连接。由于各地理数据库工作空间存在着细微的差别,因此建议您查看上述“已知局限性”部分以便更好地了解其他可能需要解决的问题。

This scenario involves updating two different workspaces in a map document. First, shapefiles are redirected to a file geodatabase called Parcels.gdb. Second, layers from a personal geodatabase are redirected to another file geodatabase called Transportation.gdb.

import arcpy
mxd = arcpy.mapping.MapDocument(r"C:\Project\Project.mxd")
mxd.replaceWorkspaces(r"C:\Project\Data", "SHAPEFILE_WORKSPACE", r"C:\Project\Data\Parcels.gdb", "FILEGDB_WORKSPACE")
mxd.replaceWorkspaces(r"C:\Project\Data\Transportation.mdb", "ACCESS_WORKSPACE", r"C:\Project\Data\Transportation.gdb", "FILEGDB_WORKSPACE")
mxd.saveACopy(r"C:\Project\Project2.mxd")
del mxd

This example is identical to the example above except that all data sources are directed to a single file geodatabase. Multiple workspace types can be redirected into a single workspace type if a value of NONE is used in place of the old_workspace_type parameter.

import arcpy
mxd = arcpy.mapping.MapDocument(r"C:\Project\Project.mxd")
mxd.replaceWorkspaces("", "NONE", r"C:\Project\Data\BackgroundData.gdb","FILEGDB_WORKSPACE")
mxd.saveACopy(r"C:\Project\Project2.mxd")

The following scenario redirects all layers from a personal geodatabase to a file geodatabase. It also fixes the SQL expressions for a layer's definitionQuery property and a label class's SQLQuery property. For example, personal geodatabases have square brackets ([]) around field names, whereas file geodatabases use double quotes (""). Wildcard characters are also replaced.

import arcpy
mxd = arcpy.mapping.MapDocument(r"C:\Project\Project.mxd")
mxd.replaceWorkspaces(r"C:\Project\Data\Parcels.mdb", "ACCESS_WORKSPACE",
                       r"C:\Project\Data\Parcels.gdb", "FILEGDB_WORKSPACE")
for lyr in arcpy.mapping.ListLayers(mxd):
    if lyr.supports("DEFINITIONQUERY"):
        lyr.definitionQuery = lyr.definitionQuery.replace("[", "\"")
        lyr.definitionQuery = lyr.definitionQuery.replace("]", "\"")
        lyr.definitionQuery = lyr.definitionQuery.replace("*", "%")

    if lyr.supports("LABELCLASSES"):
        for lblClass in lyr.labelClasses:
            lblClass.SQLQuery = lblClass.SQLQuery.replace("[", "\"")
            lblClass.SQLQuery = lblClass.SQLQuery.replace("]", "\"")
            lblClass.SQLQuery = lblClass.SQLQuery.replace("*", "%")

mxd.saveACopy(r"C:\Project\Project2.mxd")
del mxd

将单个数据集移到新的文件夹位置

有些情况下,用户只需要移动单个数据集而不是整个工作空间。有时,不用尝试更新地图文档中的所有图层,仅仅集中处理已移动的各项目就可以达到目的。

In this scenario, an individual feature class called MapIndex was moved from one file geodatabase in a folder called Data to a copy of the file geodatabase in a different folder called Data2.

import arcpy
mxd = arcpy.mapping.MapDocument(r"C:\Project\Project.mxd")
for lyr in arcpy.mapping.ListLayers(mxd):
    if lyr.supports("DATASOURCE"):
        if lyr.dataSource == r"C:\Project\Data\Parcels.gdb\MapIndex":
            lyr.findAndReplaceWorkspacePath(r"Data", r"Data2")
mxd.saveACopy(r"C:\Project\Project2.mxd")
del mxd

将单个数据集移入和移出地理数据库要素数据集。

地理数据库要素数据集是工作空间的组成部分,而且它们的名称不应包括在工作空间路径中。如果在同一工作空间中将要素类移入或移出地理数据库要素数据集,则不应更新地图文档或图层文件数据源。如果将数据集移到同一类型的其他工作空间,则只需提供新工作空间的路径而无需地理数据库要素数据集名称。如果工作空间类型不同,则对图层调用 findAndReplaceWorkspacePath 将不起作用,您需要改为调用 replaceDataSource

In this scenario, a feature class was moved from being stand alone in one file geodatabase into a feature dataset in another file geodatabase.

import arcpy
mxd = arcpy.mapping.MapDocument(r"C:\Project\Project.mxd")
for lyr in arcpy.mapping.ListLayers(mxd):
    if lyr.supports("DATASOURCE"):
        if lyr.dataSource == r"C:\Project\Data\Parcels.gdb\MapIndex":
            lyr.findAndReplaceWorkspacePath(r"Parcels.gdb", r"Transportation.gdb")
mxd.saveACopy(r"C:\Project\Project2.mxd")
del mxd

要素类被重命名

方案变更很普遍,有时要素类的名称也会发生更改。这种情况下,指向该数据集的所有图层必然会发生损坏。

In this scenario, a feature class was renamed from MajorRoads to Highways, therefore breaking individual layers in a map document or layer file. The script will evaluate only the broken data sources using the ListBrokenDataSources() function and perform the appropriate fix.

import arcpy
mxd = arcpy.mapping.MapDocument(r"C:\Project\Project.mxd")
for lyr in arcpy.mapping.ListBrokenDataSources(mxd):
    if lyr.supports("DATASOURCE"):
        if lyr.dataSource == r"C:\Project\Data\Transportation.gdb\MajorRoads":
            lyr.replaceDataSource(r"C:\Project\Data\Transportation.gdb", "FILEGDB_WORKSPACE", "Highways")
            lyr.name = "Highways"
mxd.saveACopy(r"C:\Project\Project2.mxd")
del mxd
5/10/2014