使用 arcpy.nax 创建 Python 脚本工具

需要 Network Analyst 许可。

您可以使用 arcpy.nax Python 模块自动化网络分析工作流。 使用 arcpy.nax 编写 Python 脚本后,您可以使脚本成为脚本工具,使其可以像其他地理处理工具一样运行。 在本教程中,您将学习如何编写简单网络分析工作流的代码,并配置脚本工具以运行代码。

注:

尽管在本教程中您将学习如何在 .atbx 工具箱中创建脚本工具,但您也可以使用 Python 工具箱 (.pyt) 创建包含 Pythonarcpy.nax 模块的自定义地理处理工具。 了解脚本工具和 Python 工具箱 (.pyt) 的差异。 此教程中的许多课程适用于两种类型的工具。

在本教程中,您要创建的脚本工具将运行服务区分析,并将生成的服务区面写入要素类。

注:

本教程以服务区分析为例,但是您可以创建脚本工具来运行任意类型的网络分析。

工具包括以下可供用户设置的参数:

  • 输入设施点 - 用于在周围计算服务区面的点
  • 输出面 - 工具将创建的输出要素类
  • 网络 - 用于计算服务区的网络数据集或网络分析服务
  • 出行模式 - 用于分析的出行模式
  • 中断 - 服务区行驶时间或距离限制
  • 中断单位 - 用于解释中断参数值的时间或距离单位
  • 时间 - 用于分析的日期和时间
显示您将在本教程中创建的脚本工具的对话框,其中已填充有效输入。

收集测试数据

本教程的目标是创建适用于任意输入数据的脚本工具。 没有必须使用的特定数据,但是必须具备以下条件才能测试脚本工具:

  • 网络数据集或网络分析服务的访问权限
  • 包含位于同一地理区域(作为网络)的一些测试点的点要素类

如果您没有自己的数据,则可以下载并使用提供的教程数据。

注:
确保已登录到您的 ArcGIS Online 帐户。 可以使用指定的教程网络数据集或使用涵盖分析输入数据地理的网络数据集发布的 ArcGIS OnlineArcGIS Enterprise 路径服务作为网络数据源来完成本教程。 如果您使用 ArcGIS Online,则会消耗配额

了解有关使用服务进行网络分析的更多信息

提示:

提供的教程数据包括按照本教程的步骤创建的完整脚本工具。 可以在提取的教程数据的 Tutorial\ScriptTool 文件夹中找到它。

  1. 请转至数据下载页面
  2. 单击下载按钮将文件保存到本地。
  3. 解压下载的文件。

创建工具

首先,您将在新的工具箱中创建工具并定义其输入和输出参数。

创建工具箱

创建一个工具箱以存储脚本工具。

  1. 打开 ArcGIS Pro,然后使用地图创建新工程。
  2. 目录窗格(默认情况下,位于应用程序的右侧)上,右键单击文件夹,然后选择添加文件夹连接 添加文件夹连接

    随即出现添加文件夹连接对话框。

  3. 连接到您选择的文件夹。

    您将创建工具箱并将脚本工具的 Python .py 文件存储在此文件夹中。

  4. 右键单击文件夹连接,然后单击新建 > 工具箱 (.atbx)

    将创建扩展名为 .atbx 的工具箱文件。 工具箱的名称将处于编辑模式。

  5. 将工具箱的名称更新为 TutorialScriptTool.atbx
  6. 右键单击该工具箱,然后单击属性

    工具箱属性对话框随即显示。

  7. 标注框中,将工具箱的标注更新为教程脚本工具
  8. 别名框中,将工具箱的别名更新为 TutorialScriptTool

    Python 进程中调用工具时,将使用工具箱的别名。

    对话框显示更新的
  9. 单击确定以关闭工具箱属性对话框。

在工具箱中创建脚本工具

接下来,在工具箱中创建脚本工具并更新其基本属性。

  1. 右键单击在之前部分中创建的工具箱,然后单击新建 > 脚本

    将出现工具属性对话框。

  2. 在侧面选项卡的列表中,单击常规选项卡(如果尚未选中)。
  3. 名称文本框中,输入 ServiceAreaTutorialScriptTool

    Python 进程中调用工具时,将使用此名称。 名称只能使用字母数字字符。 其中不能包含空格或特殊字符。

  4. 标注文本框中,输入服务区教程脚本工具

    标注是脚本工具的显示名称(如地理处理窗格中所示),可以包含空格。

    脚本工具的
  5. 描述文本框中,根据需要输入脚本的描述。

定义工具参数

下一步是定义工具的参数。 参数显示在工具的对话框中,用户可以使用参数选择输入数据、输出位置和其他选项。 工具运行时,参数值将发送到工具的 Python 脚本。 脚本将检索参数值并将其用于分析。

如本教程的开头部分所述,您将创建七个参数。

了解有关设置脚本工具参数的详细信息

  1. 在侧面选项卡的列表中,单击参数选项卡。
  2. 要创建输入设施点参数,请完成以下子步骤:

    用户可通过输入设施点参数选择点要素类或点图层,分析将在这些点周围计算服务区面。

    1. 单击标注列下的第一个空单元格,输入 Input Facilities,然后按 Enter 键。

      输入设施点参数已创建。 名称列中的单元格自动更新为 Input_Facilities

    2. 单击数据类型单元格中的选项按钮 选项 以打开参数数据类型对话框。
    3. 参数数据类型对话框中,从下拉菜单中选择要素图层,然后单击确定以关闭对话框。
      “参数数据类型”对话框显示选择的“要素图层”类型。

      要素图层类型的参数支持工具用户选择地图中的图层或使用要素类的目录路径作为此参数的输入。

    4. 单击过滤器列中的单元格。 如有必要,向右滚动以查找此列。
    5. 使用下拉菜单选择要素类型选项。
      为输入设施点参数选择要素类型过滤器。

      要素类型过滤器对话框随即显示。

    6. 要素类型过滤器对话框中,选中选项,然后单击确定以关闭对话框。
      选中“点”选项的“要素类型过滤器”对话框

      服务区设施点必须为点。 设置此要素类型过滤器后,输入设施点参数将仅允许将点要素类和图层作为输入。 如果用户选择面或线要素类,则工具参数将自动显示错误。

  3. 要创建输出面参数,请完成以下子步骤:

    用户可通过输出面参数选择位置,工具会将运行期间创建的服务区面要素类保存在此处。

    1. 单击标注列下的第一个空单元格,输入 Output Polygons,然后按 Enter 键。

      输出面参数已创建。 名称列中的单元格自动更新为 Output_Polygons

    2. 与为输入设施点参数定义数据类型的操作类似,单击数据类型单元格以打开参数数据类型对话框。 这一次,将数据类型设置为要素类

      要素类类型的输出参数用于选择新要素类的目录路径。

    3. 单击方向列,然后从下拉菜单中选择输出选项。

      因为此参数已配置为输出参数,因此用户可以为尚不存在的要素类选择输出文件路径和名称。

  4. 要创建网络参数,请完成以下子步骤:

    用户可通过网络参数选择用于计算服务区的网络数据集或网络分析服务。

    1. 单击标注列下的第一个空单元格,输入 Network,然后按 Enter 键。

      网络参数已创建。 名称列中的单元格自动更新为 Network

    2. 数据类型列中,将数据类型设置为网络数据源

      网络数据源参数类型支持用户选择网络数据集图层、网络数据集目录路径或网络分析服务的 URL。 对于网络数据集图层类型的参数,用户仅可以选择网络数据集图层或网络数据集目录路径,而不可以选择服务 URL。 网络数据集参数类型仅允许网络数据集目录路径,此类型更常用作输出参数类型,而非输入类型。

  5. 要创建出行模式参数,请完成以下子步骤:

    用户可通过出行模式参数选择用于分析的出行模式。 您将为此参数配置依赖关系,以使选项列表自动更新以反映使用所选网络参数值时可用的出行模式。

    1. 单击标注列下的第一个空单元格,输入 Travel Mode,然后按 Enter 键。

      出行模式参数已创建。 名称列中的单元格自动更新为 Travel_Mode

    2. 数据类型列中,将数据类型设置为网络出行模式
    3. 单击依赖关系列中的单元格。 如有必要,向右滚动以查找此列。
    4. 依赖关系列单元格的下拉菜单中,选择网络选项。
      在“出行模式”参数“依赖关系”列的下拉菜单中选择“网络”参数。

      出行模式参数现在链接至网络参数。 工具对话框将根据网络参数中设置的值自动更新出行模式参数中的选项列表。

  6. 要创建中断参数,请完成以下子步骤:

    用户可通过中断参数选择指定服务区行驶时间或距离限制的一个或多个数值。 服务区面将仅显示应用这些限制的情况下从输入设施点出发可以到达的区域。 如果输入了多个值,则会为每个设施点针对每个中断值创建一个服务区面。 例如,输出可能包含每个设施点表示周围 10 分钟和15 分钟驾驶时间的面。

    1. 单击标注列下的第一个空单元格,输入 Cutoffs,然后按 Enter 键。

      中断参数已创建。 名称列中的单元格自动更新为 Cutoffs

    2. 单击数据类型单元格中的选项按钮 选项 以打开参数数据类型对话框。
    3. 参数数据类型对话框中,从下拉菜单中选择双精度
    4. 参数数据类型对话框中,选中多个值选项。
      “参数数据类型”对话框显示已选中“双精度”类型并选中“多个值”选项。

      多个值选项支持用户为参数输入多个值。

    5. 单击确定以关闭参数数据类型对话框。

    服务区中断必须大于零。 创建表示零分钟或负数行驶时间的面没有意义。 可以在过滤器列中选择范围选项以将数值参数配置为使用范围过滤器。 如果用户选择了范围之外的数字,则会自动显示错误消息。 但是,范围过滤器仅支持包含两者的数值范围,但您希望排除下限 0,同时允许小数值(例如 0.5)。 因此,您将使用 Python 代码通过工具验证构建自己的范围过滤器,而不是使用提供的范围过滤器。 本课程的后续部分将介绍此内容。

  7. 要创建中断单位参数,请完成以下子步骤:

    用户可通过中断单位参数指定用于解释中断参数值的测量单位。

    1. 单击标注列下的第一个空单元格,输入 Cutoff Units,然后按 Enter 键。

      中断单位参数已创建。 名称列中的单元格自动更新为 Cutoff_Units

    2. 数据类型列中,确认数据类型已设置为字符串,在必要时可更新设置。

    要为参数配置选项列表,您可以在过滤器列中选择值列表选项,并输入要显示给客户的值列表。 但是,对于中断单位参数,您希望选项列表根据用户所选的出行模式参数值的单位(时间或距离)而动态变化。 在下一部分中,您将使用 Python 代码通过工具验证构建动态选项列表。

  8. 要创建时间参数,请完成以下子步骤:

    用户可通过时间参数设置分析的日期和时间。 此为车辆或行人离开设施点的日期和时间。 因为分析不是始终和时间有关,因此您可将此参数配置为可选参数。 用户可以在需要时设置此参数,也可以将其留空。

    1. 单击标注列下的第一个空单元格,输入 Time Of Day,然后按 Enter 键。

      时间参数已创建。 名称列中的单元格自动更新为 Time_Of_Day

    2. 数据类型列中,将数据类型设置为日期
    3. 单击类型列中的单元格,然后从下拉菜单中选择可选选项。

      因为此参数已配置为可选参数,所以用户可以将其留空而不会收到错误消息。

  9. 检查参数配置。 其外观应该与下图类似:
    脚本工具的完整参数配置
  10. 单击工具属性对话框上的确定以提交更改并关闭对话框。

验证工具参数

现在,您已经配置了工具参数,应该验证其是否如期运行。 因为尚未编写工具的运行代码,所以还无法运行工具,虽然还需执行一些操作来为某些参数配置自定义验证,但现在请花费一些时间验证到目前为止完成的参数配置。

  1. 目录窗格中,在工具箱中查找新的服务区教程脚本工具
    “目录”窗格中的工具箱和脚本工具
  2. 双击工具以将其打开。

    服务区教程脚本工具将在地理处理窗格中打开。 您可以看到在之前部分中创建的七个参数。

    “地理处理”窗格中的脚本工具对话框显示所有参数。
  3. 检查每个参数并确认其行为符合预期。
    1. 检查输入设施点参数。

      您应该仅可以使用此参数选择点图层和点要素类。 面要素类和线要素类不会显示在此选项列表中,如果您手动输入,则会看到一条错误消息,指示输入的几何类型无效。

      提示:
      如果地图中没有要素图层,则您不会看到选项列表。 如要查看参数的行为,请向地图添加一些图层。

    2. 检查输出面参数。

      您应该可以选择输出位置。 如果选择现有要素类,则参数应显示一条警告,指示输出已存在。

    3. 检查网络出行模式参数。

      出行模式参数最初应该是空的,并且没有选项列表。 您应该可以使用网络参数选择网络数据集图层、网络数据集目录路径或网络分析服务的 URL。

    4. 网络参数选择一个值。

      出行模式参数的选项列表将自动更新,包含与网络值相关联的可用出行模式的列表。

    5. 检查中断参数。

      您可以输入多个数值。

      当前,您可以输入负数或 0,而参数不会显示错误。 稍后,您将使用工具验证更新此行为。

    6. 检查中断单位参数。

      当前,参数不会显示选项列表。 稍后,您将使用工具验证以根据出行模式值提供一个选项列表。

    7. 检查时间参数。

      与其他参数不同,因为此参数为可选参数,所以当其为空时不会显示红色星号。 您可以使用日期和时间选择器工具或手动输入日期和时间。

编写 Python 脚本

在本部分中,您将编写脚本工具将运行的 Python 脚本。 脚本工具将执行以下操作:

  • 检索输入参数。
  • 如有必要,检出 ArcGIS Network Analyst 扩展模块许可。
  • 初始化服务区分析。
  • 设置服务区分析设置。
  • 加载输入设施点。
  • 求解服务区分析并处理求解错误。
  • 将输出服务区面写入要素类。

  1. 在之前创建工具箱的文件夹中创建名为 ServiceAreaTutorialScriptTool.py 的文件,然后根据您的选择,在集成开发环境 (IDE) 或文本编辑器中将其打开以进行编辑。

    您可以在此练习中使用任意 Python IDE 或文本编辑器。

  2. 请阅读以下小节以了解将用于脚本工具的代码组成部分。
  3. 本部分的底部包含完整代码。 将此代码复制并粘贴到 ServiceAreaTutorialScriptTool.py 文件并保存文件。

    警告:
    将其粘贴到 ServiceAreaTutorialScriptTool.py 文件后,验证代码的缩进是否正确。

检索输入参数

脚本工具代码必须使用 GetParameterGetParameterAsText ArcPy 函数从工具参数检索用户的输入。 GetParameter 函数会使用为参数定义的数据类型返回参数值,而 GetParameterAsText 函数始终以字符串形式返回参数值。

在此代码片段中,脚本会检索工具参数并将其分配到变量。 当您想要保留输入参数的数据类型时,可使用 GetParameter 函数。 检索输入设施点和网络数据源时可使用 GetParameterAsText 函数,即使这些输入可能是图层,因为图层的字符串名称可以用作地理处理工具和 ArcPy 函数的有效输入。 此处用于 GetParameterGetParameterAsText 的索引值与您之前定义的参数顺序一致。

input_facilities = arcpy.GetParameterAsText(0)
output_polygons = arcpy.GetParameterAsText(1)
network = arcpy.GetParameterAsText(2)
travel_mode = arcpy.GetParameter(3)
cutoffs = arcpy.GetParameter(4)
cutoff_units = arcpy.GetParameterAsText(5)
time_of_day = arcpy.GetParameter(6)

检出 ArcGIS Network Analyst 扩展模块许可

如果工具用户为ArcGIS Network Analyst 扩展模块参数选择了网络数据集,则需要 ArcGIS Network Analyst 扩展模块许可。 如果用户选择通过指定门户 URL 使用网络分析服务,则不需要 ArcGIS Network Analyst 扩展模块许可。

在此代码片段中,如果输入网络不是以 http 为开头(即推测为网络分析服务),脚本将尝试检出扩展模块许可。 如果扩展模块不可用,则会报告错误。 本教程的后续部分会介绍错误的处理方法。

# Check out the Network Analyst extension license if the input network
# is a local network dataset and not a service.
if not network.startswith("http"):
    if arcpy.CheckExtension("network") == "Available":
        arcpy.CheckOutExtension("network")
    else:
        # Throw an error if the license cannot be checked out.
        arcpy.AddError("The Network Analyst license is unavailable.")
        raise CustomError

初始化服务区分析

使用 arcpy.nax 的网络分析工作流的第一步是使用指定的网络数据源实例化求解程序的分析对象。

了解有关使用 arcpy.nax 的网络分析工作流中步骤的详细信息

在此代码片段中,将初始化服务区分析对象。

# Instantiate the ServiceArea solver object
sa = arcpy.nax.ServiceArea(network)

设置服务区分析设置

对于此分析,您将对不希望用户更改的服务区分析设置进行硬编码。 将根据工具参数中用户的其他选择设置其他参数。

了解有关服务区分析设置的详细信息

在此代码片段中,一些分析设置已经过硬编码。 出行模式、时间和默认阻抗中断将使用从工具参数中检索到的值设置。

# Hard-code some non-default Service Area settings that we don't want
# the user to change
sa.geometryAtCutoff = arcpy.nax.ServiceAreaPolygonCutoffGeometry.Disks
sa.polygonBufferDistance = 150
sa.polygonBufferDistanceUnits = arcpy.nax.DistanceUnits.Feet

# Set analysis properties chosen by the user and passed in via tool
# parameters
sa.travelMode = travel_mode
sa.timeOfDay = time_of_day
sa.defaultImpedanceCutoffs = cutoffs

您还需要设置中断单位,但这需要一些额外的处理。 工具参数将以字符串形式配置,您需要将基于字符串的单位名称转换为适当的 TimeUnitsDistanceUnits 枚举值。

在此代码片段中,将根据从工具参数中传入的值设置时间单位或距离单位,在两个函数的帮助下,这些值由字符串值转换为适当的枚举值。 在此示例工具中,将仅支持有限的可用时间单位和距离单位列表。 本教程的后续部分会进一步介绍工具参数。

# Do special handling of cutoff units to convert them to the correct
# arcpy.nax enum
if cutoff_units in ["Hours", "Minutes"]:
    sa.timeUnits = convert_time_units_to_nax(cutoff_units)
elif cutoff_units in ["Kilometers", "Meters", "Miles", "Yards", "Feet"]:
    sa.distanceUnits = convert_distance_units_to_nax(cutoff_units)

在此代码片段中,定义了两个函数以将基于字符串的单位名称转换为正确的枚举值。 其中并没有包括所有可用的 TimeUnitsDistanceUnits 枚举值,因为在本示例中,您将限制显示给用户的单位选项。 如果传递了无效单位,函数会报告错误。

def convert_time_units_to_nax(time_units_str):
    """Convert string-based time units to the correct arcpy.nax enum."""
    if time_units_str == "Hours":
        return arcpy.nax.TimeUnits.Hours
    if time_units_str == "Minutes":
        return arcpy.nax.TimeUnits.Minutes
    arcpy.AddError(f"Invalid time units: {time_units_str}")
    raise CustomError


def convert_distance_units_to_nax(dist_units_str):
    """Convert string-based distance units to the correct arcpy.nax enum."""
    if dist_units_str == "Kilometers":
        return arcpy.nax.DistanceUnits.Kilometers
    if dist_units_str == "Meters":
        return arcpy.nax.DistanceUnits.Meters
    if dist_units_str == "Miles":
        return arcpy.nax.DistanceUnits.Miles
    if dist_units_str == "Yards":
        return arcpy.nax.DistanceUnits.Yards
    if dist_units_str == "Feet":
        return arcpy.nax.DistanceUnits.Feet
    arcpy.AddError(f"Invalid distance units: {dist_units_str}")
    raise CustomError

加载输入设施点

接下来,使用 load 方法将用户的设施点添加到服务区分析。

了解有关更多分析输入设置方式的详细信息

在此代码片段中,输入设施点将使用 load 方法添加到服务区分析。 ServiceAreaInputDataType.Facilities 枚举值将用作 load 方法的参数,以指示输入应添加为设施点。

# Load the input facilities
sa.load(arcpy.nax.ServiceAreaInputDataType.Facilities, input_facilities)

求解服务区分析并处理求解错误

现在,服务区设置已配置完成,输入数据已经加载,您可以求解分析并检索结果。 使用求解方法求解分析会生成一个 ServiceAreaResult 对象的实例,其中包含分析输出的属性和处理方法。

在此代码片段中,将使用 solve 方法求解服务区分析。 result 变量可用于处理分析输出。

# Solve the analysis
result = sa.solve()

有时候,分析可能会失败,或者运行成功但向用户返回警告消息,提示可能存在的问题。 在您的工具中,您希望向客户返回求解程序错误和警告。 如果求解程序失败,您希望工具停止运行。

在此代码段中,ServiceAreaResult 对象的 solverMessages 方法用于使用 MessageSeverity.Warning 枚举值检索警告消息。 收到的每个警告消息都将使用 AddWarning 函数添加到工具的警告消息。

# Print warning messages if there are any
for warning in result.solverMessages(arcpy.nax.MessageSeverity.Warning):
    arcpy.AddWarning(warning[1])

在此代码片段中,您将使用 ServiceAreaResult 对象的 solveSucceeded 属性检查服务区分析是否成功。 如果未成功,将使用 solverMessages 方法检索错误消息,然后 AddError 函数会将其添加到工具错误中。 此外,会报告一个错误以使工具停止运行。 稍后会进一步介绍错误的处理方法。

# Handle failed solves
if not result.solveSucceeded:
    arcpy.AddError("The Service Area solve failed.")
    # Print error messages and stop the tool from running further
    for error in result.solverMessages(arcpy.nax.MessageSeverity.Error):
        arcpy.AddError(error[1])
    # Stop tool run by raising an error
    raise CustomError

将输出服务区面写入要素类

最后,您将使用 export 方法将服务区分析的输出写入用户指定的要素类。 还将使用计数 method 计算输出中面的数量并将此信息写为消息。

了解其他访问和处理分析输出的方式

在此代码片段中,已使用 AddMessage 函数将输出中的面数量写为消息,并且服务区面将导出到要素类中。 ServiceAreaOutputDataType.Polygons 枚举值用于处理输出面,而不是其他分析输出类型。

# Add a message with the total number of polygons that were generated
# in the analysis
num_polygons = result.count(
    arcpy.nax.ServiceAreaOutputDataType.Polygons)
arcpy.AddMessage(f"Number of polygons generated: {num_polygons}.")

# Export the Service Area polygons to the output feature class
result.export(
    arcpy.nax.ServiceAreaOutputDataType.Polygons, output_polygons)

处理错误

遇到已知错误时,您希望工具停止运行并生成可提供帮助的错误消息。 为此,您将提出自定义异常情况,并将工具的运行代码封装在 try/except 代码块中。

在此代码片段中,定义了自定义异常情况。 此代码不会执行任何操作,但是当遇到已知问题时,可用于停止工具的运行。

class CustomError(Exception):
    pass

工具的运行代码封装在 try/except 代码块中。 如果发现 CustomError,代码不会执行任何操作,因为错误信息已经添加到工具错误中。 如果发生未知错误,则代码将检索 traceback 并将其添加为工具错误以协助调试。

try:

    [...]

except CustomError:
    # We caught a known error and already added the message. Do nothing.
    pass

except Exception:
    # An unknown error occurred. Add the traceback as an error message.
    arcpy.AddError(
        "An unknown error occurred when generating Service Areas.")
    import traceback
    arcpy.AddError(traceback.format_exc())

汇总

以下代码片段包含完整的 Python 脚本,其中包含上方所述的所有组成部分。 您可以将此代码复制并粘贴到 ServiceAreaTutorialScriptTool.py 文件中。

import arcpy


class CustomError(Exception):
    pass


def convert_time_units_to_nax(time_units_str):
    """Convert string-based time units to the correct arcpy.nax enum."""
    if time_units_str == "Hours":
        return arcpy.nax.TimeUnits.Hours
    if time_units_str == "Minutes":
        return arcpy.nax.TimeUnits.Minutes
    arcpy.AddError(f"Invalid time units: {time_units_str}")
    raise CustomError


def convert_distance_units_to_nax(dist_units_str):
    """Convert string-based distance units to the correct arcpy.nax enum."""
    if dist_units_str == "Kilometers":
        return arcpy.nax.DistanceUnits.Kilometers
    if dist_units_str == "Meters":
        return arcpy.nax.DistanceUnits.Meters
    if dist_units_str == "Miles":
        return arcpy.nax.DistanceUnits.Miles
    if dist_units_str == "Yards":
        return arcpy.nax.DistanceUnits.Yards
    if dist_units_str == "Feet":
        return arcpy.nax.DistanceUnits.Feet
    arcpy.AddError(f"Invalid distance units: {dist_units_str}")
    raise CustomError


def generate_service_areas():
    """Generate Service Area polygons."""
    try:
        input_facilities = arcpy.GetParameterAsText(0)
        output_polygons = arcpy.GetParameterAsText(1)
        network = arcpy.GetParameterAsText(2)
        travel_mode = arcpy.GetParameter(3)
        cutoffs = arcpy.GetParameter(4)
        cutoff_units = arcpy.GetParameterAsText(5)
        time_of_day = arcpy.GetParameter(6)

        # Check out the Network Analyst extension license if the input network
        # is a local network dataset and not a service.
        if not network.startswith("http"):
            if arcpy.CheckExtension("network") == "Available":
                arcpy.CheckOutExtension("network")
            else:
                # Throw an error if the license cannot be checked out.
                arcpy.AddError("The Network Analyst license is unavailable.")
                raise CustomError

        # Instantiate the ServiceArea solver object
        sa = arcpy.nax.ServiceArea(network)

        # Hard-code some non-default Service Area settings that we don't want
        # the user to change
        sa.geometryAtCutoff = arcpy.nax.ServiceAreaPolygonCutoffGeometry.Disks
        sa.polygonBufferDistance = 150
        sa.polygonBufferDistanceUnits = arcpy.nax.DistanceUnits.Feet

        # Set analysis properties chosen by the user and passed in via tool
        # parameters
        sa.travelMode = travel_mode
        sa.timeOfDay = time_of_day
        sa.defaultImpedanceCutoffs = cutoffs
        # Do special handling of cutoff units to convert them to the correct
        # arcpy.nax enum
        if cutoff_units in ["Hours", "Minutes"]:
            sa.timeUnits = convert_time_units_to_nax(cutoff_units)
        elif cutoff_units in ["Kilometers", "Meters", "Miles", "Yards", "Feet"]:
            sa.distanceUnits = convert_distance_units_to_nax(cutoff_units)

        # Load the input facilities
        sa.load(arcpy.nax.ServiceAreaInputDataType.Facilities, input_facilities)

        # Solve the analysis
        result = sa.solve()

        # Print warning messages if there are any
        for warning in result.solverMessages(arcpy.nax.MessageSeverity.Warning):
            arcpy.AddWarning(warning[1])

        # Handle failed solves
        if not result.solveSucceeded:
            arcpy.AddError("The Service Area solve failed.")
            # Print error messages and stop the tool from running further
            for error in result.solverMessages(arcpy.nax.MessageSeverity.Error):
                arcpy.AddError(error[1])
            # Stop tool run by raising an error
            raise CustomError

        # Add a message with the total number of polygons that were generated
        # in the analysis
        num_polygons = result.count(
            arcpy.nax.ServiceAreaOutputDataType.Polygons)
        arcpy.AddMessage(f"Number of polygons generated: {num_polygons}.")

        # Export the Service Area polygons to the output feature class
        result.export(
            arcpy.nax.ServiceAreaOutputDataType.Polygons, output_polygons)

    except CustomError:
        # We caught a known error and already added the message. Do nothing.
        pass

    except Exception:
        # An unknown error occurred. Add the traceback as an error message.
        arcpy.AddError(
            "An unknown error occurred when generating Service Areas.")
        import traceback
        arcpy.AddError(traceback.format_exc())


if __name__ == "__main__":
    generate_service_areas()

配置脚本工具以运行 Python 脚本文件

之前,您创建了脚本工具并定义了其参数。 然后,编写了 Python 脚本以执行分析。 现在,您必须配置工具以运行 Python 脚本。 当您运行工具时,工具将运行 Python 脚本中的代码,Python 脚本会检索从工具参数传入脚本的输入并完成分析。

  1. 右键单击脚本工具并单击属性以打开脚本工具的属性对话框。

    将出现工具属性对话框。

  2. 在侧面选项卡的列表中,单击执行选项卡。
  3. 单击文件夹按钮 文件夹 并浏览至 ServiceAreaTutorialScriptTool.py 脚本文件的位置。

    提示:
    如果 ServiceAreaTutorialScriptTool.py 文件未显示在浏览对话框中,单击顶部地址栏旁边的刷新按钮。用于刷新浏览对话框的按钮

    执行选项卡中的代码将更新以显示 Python 脚本文件的内容。 工具现已配置为运行 Python 脚本。

自定义工具验证

验证是指在工具运行之前检查并更新工具参数值的过程。 验证可以确保为参数输入的值具有正确类型且在正确范围内。 验证还可以更新参数选项列表,甚至根据其他参数的值隐藏或显示参数。

一些验证过程内置于某些参数类型和配置中。 例如,您之前为输入设施点参数配置了要素类型过滤器,以便仅接受点要素类。 当用户为此参数选择输入时,验证过程会验证输入的类型是否正确,如果不正确,则显示一条错误消息。

但是,脚本工具的作者有时需要实现更加复杂的验证或自定义验证。 ToolValidator 类可用于实现此目的。 可以使用自定义代码更新这一特殊的 Python 类,从而更新参数并执行检查,在必要时发出错误和警告消息。

在本教程中,您将在脚本工具中编写 ToolValidator 类的程序代码,使其在中断参数值小于或等于 0 时报告错误,并根据出行模式参数中的值使用一列时间单位或距离单位更新中断单位参数的选项列表。

ToolValidator 代码独立于您之前编写的由工具运行的 Python 脚本,可以单独对其进行访问和编辑。

注:

您也可以在 Python 工具箱 (.pyt) 中创建自定义地理处理工具。 在本例中,验证代码和工具运行代码包含在同一个 Python 脚本中。

了解有关脚本工具验证功能的详细信息

查找 ToolValidator 类并将其打开以进行编辑

创建脚本工具时,将自动创建 ToolValidator 类。 您可以通过脚本工具的属性对话框访问此类并将其打开以进行编辑。

  1. 如有必要,右键单击脚本工具并单击属性以打开脚本工具的属性对话框。

    将出现工具属性对话框。

  2. 在侧面选项卡的列表中,单击验证选项卡。

    脚本工具的 ToolValidator 类随即可见。 您可以直接在对话框中编辑代码,也可以单击在脚本编辑器中打开按钮,以在 IDE 或文本编辑器中以单独的文件打开 ToolValidator

    提示:

    可以使用地理处理选项中的脚本编辑器设置控制使用哪个 IDE 或文本编辑器打开文件。

如果中断参数值小于或等于 0,则会报告错误。

ToolValidator 类的 updateMessages 方法可用于检查参数值并添加错误或警告消息。 在本部分中,您将修改 updateMessages 方法,使其在中断参数值小于或等于 0 时报告错误。

  1. ToolValidator 类中查找 updateMessages 方法。

    默认情况下,updateMessages 方法仅包含一个返回语句。 不会执行自定义验证。

  2. 更改 updateMessages 方法以使用以下代码:

    警告:
    将代码粘贴到 ToolValidator 类中后,请检查验证代码的缩进。

    def updateMessages(self):
        # Customize messages for the parameters.
        # This gets called after standard validation.
    
        # Raise an error if any of the cutoffs are <= 0
        cutoffs_param = self.params[4]
        if cutoffs_param.valueAsText:
            for cutoff in cutoffs_param.values:
                if cutoff <= 0:
                    cutoffs_param.setErrorMessage("Cutoffs must be positive.")
                    break
    
        return

    在此代码片段中,中断参数的值小于或等于 0 时,会向其添加一条错误消息。

    中断参数使用 self.params 变量检索,此变量是内置于 ToolValidator 类的工具参数列表。 中断参数是第五个参数,因此可使用该列表的索引 4 访问。 检索到的值是 Parameter 对象。

    如果参数为空,则无需检查该值。 Parameter 对象的 valueAsText 属性可以快速确定参数是否为空。

    因为此为多值参数,所以需使用 values 属性检索值列表。 代码会对参数的当前值进行迭代。

    如果中断值小于或等于 0,则使用 Parameter 对象的 setErrorMessage 设置错误消息。 此消息将显示在工具用户界面的参数上。

    如果遇到无效值,则 break 语句将停止迭代。 发现无效值后,没有必要再检查其余的值。

    注:
    即使您添加了自定义验证代码,与每个参数关联的基础内部验证也依旧适用。 例如,由于您将中断参数定义为双精度型,所以内部验证将自动检查输入是否是有效数值,并在需要时报告错误。 为参数添加的任何自定义验证都是对为该参数类型自动提供的内部验证的补充。

使用一列时间或距离单位填充中断单位参数。

出行模式包括多种设置,这些设置用于控制如何为网络中的行程建模。 它决定了查找网络中最短路径时应优化哪个变量。 通常情况下,会采用时间或距离度量,尽管网络数据集也可以配置为优化其他类型的变量,例如行程的能耗成本或货币成本。

用户可通过中断参数指定服务区出行成本限制的数值。 用户可通过中断单位参数指定用于解释中断值的测量单位。

在工具中,您希望为用户提供有效单位的列表;但如果所选出行模式优化时间,则您希望仅显示时间单位,而如果所选出行模式优化距离,则您希望仅显示距离单位。 在出行模式优化某些其他类型成本的极少数情况下,您不希望显示时间或距离单位。

ToolValidator 类的 updateParameters 方法可用于更新参数选项列表。 在本部分中,您将更改 updateParameters 方法,以根据出行模式参数的值使用适当的单位列表设置中断单位选项列表。

  1. ToolValidator 类中查找 updateParameters 方法。

    默认情况下,updateParameters 方法仅包含一个返回语句。 不会更新参数。

  2. 更改 updateParameters 方法以使用以下代码:

    警告:
    将代码粘贴到 ToolValidator 类中后,请检查验证代码的缩进。

    def updateParameters(self):
        # Modify parameter values and properties.
        # This gets called each time a parameter is modified, before
        # standard validation.
    
        # Set filter list of units in cutoff units parameter based on what type
        # of travel mode is selected
        travel_mode_param = self.params[3]
        cutoff_units_param = self.params[5]
        if travel_mode_param.valueAsText:
            try:
                tm_object = travel_mode_param.value
                if tm_object.impedance == tm_object.timeAttributeName:
                    # The impedance has units of time, so present time units as
                    # options
                    cutoff_units_param.filter.list = ["Hours", "Minutes"]
                elif tm_object.impedance == tm_object.distanceAttributeName:
                    # The impedance has units of distance, so present distance
                    # units as options
                    cutoff_units_param.filter.list = [
                        "Kilometers", "Meters", "Miles", "Yards", "Feet"]
                else:
                    # The impedance has units that are neither time nor
                    # distance, so present only one option, "Other". The
                    # Service Area cutoffs will be interpreted in the impedance
                    # units.
                    cutoff_units_param.filter.list = ["Other"]
            except Exception:
                pass
    
        return

    在此代码片段中,中断单位参数中可用值的列表已基于出行模式参数的当前值进行更新。

    参数使用 self.params 变量检索。 出行模式参数位于索引 3(第四个参数),中断单位参数位于索引 5(第六个参数)。

    如果出行模式参数为空,则不需要更新中断单位参数。 Parameter 对象的 valueAsText 属性可以快速确定参数是否为空。

    出行模式参数值使用 Parameter 对象的 value 属性检索为 TravelMode 对象。

    要确定出行模式优化的是时间、距离还是其他测量单位,最简单的方式是将 TravelMode 对象的 impedance 属性与 timeAttributeNamedistanceAttributeName 属性进行比较。 impedance 属性会返回优化的网络属性的名称。 出行模式还包含一个默认时间属性 (timeAttributeName) 和距离属性 (distanceAttributeName)。 如果 impedancetimeAttributeName 匹配,则出行模式优化时间,因此中断单位参数将更新为显示一列时间单位。 如果 impedancedistanceAttributeName 匹配,则出行模式优化距离,因此中断单位参数将更新为显示一列距离单位。 如果 impedance 与以上两项均不匹配,则出行模式优化其他变量,因此服务区中断将使用这些单位解释。 中断单位参数将更新为仅显示一个选项:其他

    在所有情况下,选项列表均通过设置中断单位 Parameter 对象的 filter.list 属性指定。

    代码块封装在 try/except 块中。 如果在读取出行模式时遇到错误,则代码不会执行操作,也不会更新选项列表。

验证工具验证

在本部分中,您将保存并测试验证代码。

  1. 如果您之前在 IDE 或文本编辑器中打开了 ToolValidator 类,请保存并关闭文件。
  2. 单击工具属性对话框上的确定以确认更改。
  3. 目录窗格中,双击工具以将其打开。

    服务区教程脚本工具将在地理处理窗格中打开。

  4. 中断参数中输入小于或等于 0 的值。

    中断参数上将显示错误符号。 如果您将鼠标悬停在符号上或单击符号,错误消息会显示在工具提示中。

  5. 当您在出行模式参数中选择值时,验证中断单位选项列表是否更新为显示正确的单位列表。
    1. 网络参数选择一个值。

      提示:
      如果您使用教程提供的数据,请针对网络参数使用网络数据集 SanFrancisco.gdb/Transportation/Streets_ND。 您可以使用网络数据集的目录路径,也可以先将其添加到地图,然后使用其图层制图表达作为工具输入。

      网络参数为空时,出行模式参数不包含选项列表。 为网络参数输入值后,出行模式参数的选项列表将自动更新,包含与网络值相关联的可用出行模式的列表。

    2. 出行模式参数选择一个值。

      出行模式参数为空时,中断单位参数不包含选项列表。 为出行模式参数输入值后,中断单位参数选项列表将自动更新为显示一列时间单位或距离单位(或单个选项其他)。

运行此工具

您已创建并配置脚本工具,编写工具代码并自定义工具验证。 工具现在已经可以运行,并且可以像其他地理处理工具一样供您使用。 除了在地理处理窗格中运行工具之外,您还可以将其添加到模型,通过 Python 脚本调用工具,或将其共享为 Web 工具

  1. 如有必要,在目录窗格中双击工具以将其打开。
  2. 为每个工具参数选择有效值。

    确保用于输入设施点参数的点与用于网络参数的网络位于相同的地理区域。

    提示:
    如果您使用教程提供的数据,请使用 SanFrancisco.gdb 地理数据库中的数据集测试工具。 消防站的点要素类 SanFrancisco.gdb/Analysis/FireStations 可用于输入设施点参数,而数据集 SanFrancisco.gdb/Transportation/Streets_ND 可用于网络参数。 您可以使用数据的目录路径,也可以先将数据添加到地图,然后使用图层作为工具输入。 消防站响应时间建模适合使用两到四分钟作为中断值。

  3. 单击工具底部的运行按钮。

    工具成功运行并将面图层添加到地图。

    注:
    工具可能生成警告消息。