自定义赋值器允许在使用 Python 脚本进行求解期间更改网络元素的成本、限制和描述符属性值。 可以查询元素的基值并根据需要进行更新,或者根据其他输入设置不同的值。 由此可在求解时设置属性值,而无需更新基础网络数据集或源要素。
自定义赋值器可用于执行以下操作:
- 通过查询另一个数据库中的表来调整街道的成本
- 根据从外部文件读取的 OID 限制街道要素
- 根据一天中的时间动态调整街道成本
自定义赋值器的实现方式是创建一个从 arcpy.nax.AttributeEvaluator 继承而来的 Python 类,并将 Python 类与特定的网络数据集关联起来。 可以与成本、限制或描述符属性相关联。 可以更新启用时间的求解以及未启用时间的求解的边、交汇点或转弯。 根据所做的更改,更新可能会改变成本和分析过程中找到的路径。
单个网络属性应该仅具有与其相关联的单个自定义赋值器,但任意数量的网络属性都可以具有关联的自定义赋值器。 当实现自定义赋值器时,可以使用多个对象来收集有关网络和元素的信息,例如 Attribute、Edge、Junction 等。
自定义赋值器类
通过定义一个从 arcpy.nax.AttributeEvaluator 继承的类并至少实现一种元素值方法(例如,edgeValue 或 edgeValueAtTime)来创建自定义赋值器。 自定义赋值器还可以实现 __init__、attach 和 refresh 方法。
自定义赋值器方法
以下小节描述了可用于在求解时设置属性值的方法。
初始化程序
可以实现初始化程序 __init__ 方法。 如果已实现,则在添加附加初始化逻辑之前必须显式调用基类的初始化程序方法。 如果未实现,则基类的初始化程序方法将自动调用。
基类初始化程序将使用传入的属性名称和源名称来设置对象上的 self.attributeName 和 self.sourceNames 属性。
class CustomEvaluatorExample(arcpy.nax.AttributeEvaluator):
"""Example custom evaluator."""
def __init__(self, attributeName, sourceNames=None):
"""Example initializer."""
super().__init__(attributeName, sourceNames)
# Do additional custom initialization
附加
当自定义赋值器与网络数据集相关联时,将调用内部 attach 方法。 内部代码将查询网络数据集属性以获取指定的属性名称。 如果找到属性名称,则会将 self.attribute 属性设置为该属性的索引值;否则,自定义赋值器将不会与网络数据集相关联,并且不会调用其他类方法(包括所有由用户实现的 attach 方法)。
在自定义赋值器中实现的 attach 方法可用于检查和验证网络数据集,以确保其符合自定义赋值器代码的要求,例如检查其他属性是否存在。 如果网络数据集有效,则 attach 方法将返回 True;否则,将返回 False。 如果返回 False,则自定义赋值器将不会与网络数据集相关联,并且不会调用其他方法。
在自定义赋值器中实现 attach 方法为可选做法。
注:
可以多次打开网络数据集,具体取决于应用程序用于访问网络数据集的线程数量。
刷新
在每次求解开始时,在评估任何元素之前,都会调用自定义赋值器的 refresh 方法。 在每次求解时都会发生变化的任何内部状态(例如,缓存值或非网络数据集验证)都可以在此方法中设置。 self.networkQuery 属性在此方法中可用。 当 attach 方法成功完成后,将在内部设置此属性。
在自定义赋值器中实现 refresh 方法为可选项。
值方法
可以实现以下值方法:edgeValue、edgeValueAtTime、junctionValue、junctionValueAtTime、turnValue 和 turnValueAtTime。 前缀为 edge 的方法会影响边源的属性值,前缀为 junction 的方法会影响交汇点源的属性值,而前缀为 turn 的方法会影响转弯源的属性值。 在启用时间的求解过程中,会调用具有 AtTime 后缀的方法,在不使用时间时,会调用没有该后缀的方法。
通常,在求解过程中,当对属性进行求值并且附加了自定义赋值器时,它将覆盖主要赋值器。 求解程序引擎将在剩余分析中使用从相关元素方法(例如 edgeValue)返回的值。 借助这些值方法,可以实现自定义逻辑以设置元素属性的最终值。
ValueAtTime 方法提供了一个日期时间参数,即元素沿潜在路径遇到的日期和时间。 在求解过程中,可能会针对每个元素多次调用这些方法。 这些方法中的任何代码都应该可执行。
必须至少实现这些值方法之一。
示例
以下示例显示了自定义赋值器类的基本实现。
示例 1:以下代码是一个自定义赋值器类,对于时间中性求解,它会将所有已求值边上指定属性的成本加倍,而无需实现任何可选方法:
import arcpy
class EdgeCustomizer(arcpy.nax.AttributeEvaluator):
"""Defines a custom evaluator that multiplies the edge cost by 2."""
def edgeValue(self, edge: arcpy.nax.Edge):
"""Multiplies the edge cost by 2."""
base_value = self.networkQuery.attributeValue(edge, self.attribute)
return base_value * 2
示例 2:以下代码是一个自定义赋值器类,对于时间中性求解和启用时间的求解,它会将所有已求值边上指定属性的成本加倍,同时最小化可选方法的实现:
import datetime
from typing import Union, Optional, List
import arcpy
class EdgeCustomizer(arcpy.nax.AttributeEvaluator):
"""Defines a custom evaluator that multiplies the edge cost by 2."""
def __init__(self, attributeName: str, sourceNames: Optional[List] = None):
"""Example initializer."""
super().__init__(attributeName, sourceNames)
# Do additional custom initialization
def attach(self, network_query: arcpy.nax.NetworkQuery) -> bool:
"""Connect to and validate the network dataset."""
# Do additional validation checks before returning Boolean
return True
def refresh(self) -> None:
"""Reset internal state before solve."""
# Reset internal state in this method as needed
pass
def edgeValue(self, edge: arcpy.nax.Edge):
"""Multiplies the edge cost by 2."""
base_value = self.networkQuery.attributeValue(edge, self.attribute)
return base_value * 2
def edgeValueAtTime(
self, edge: arcpy.nax.Edge,
time: datetime.datetime, time_usage: arcpy.nax.NetworkTimeUsage
) -> Union[int, float, bool]:
"""Multiplies the edge cost by 2 when the solve uses a time of day."""
base_value_at_time = self.networkQuery.attributeValue(
edge, self.attribute, time_usage, time)
return base_value_at_time * 2
将自定义赋值器与网络数据集相关联
有两种方法可以部署自定义赋值器,使其与网络数据集相关联,并在求解期间调用自定义逻辑。 这两种方法称为临时方法和持久方法。
提示:
首先创建自定义赋值器类,然后将其作为临时自定义赋值器进行测试。 然后在诸如 Visual Studio Code 等编辑器中以调试模式运行该脚本。 当其按预期运行后,如果需要,可以将其设为持久化自定义赋值器。 然后,验证持久化自定义赋值器以确保一切正常运行。
临时自定义赋值器
临时自定义赋值器仅与在脚本内创建的网络数据集对象相关联;它们不会永久保存到网络数据集。 在执行求解的脚本中,会使用网络数据集对象的 customEvaluators 属性配置临时自定义赋值器。
临时自定义赋值器可用于必须从 Python 脚本调用自定义赋值器的应用程序。 它们也可以用于开发和调试持久化自定义赋值器。
配置临时自定义赋值器
要设置临时自定义赋值器,请创建一个自定义赋值器对象,然后使用网络数据集对象的 customEvaluators 属性将自定义赋值器与其关联。
下方示例显示了如何实例化自定义赋值器对象,该对象可自定义 TravelTime 网络属性并将其关联至网络数据集对象。 要在求解时调用自定义赋值器,需使用网络数据集对象实例化路径求解程序对象。
# Instantiate a custom evaluator object that will customize the
# TravelTime cost attribute
travel_time_customizer = EdgeCustomizer("TravelTime")
# Create a network dataset object
network_dataset = arcpy.nax.NetworkDataset(
r"C:\Data\Tutorial\SanFrancisco.gdb\Transportation\Streets_ND")
# Attach the custom evaluator object to the network dataset
network_dataset.customEvaluators = [travel_time_customizer]
# Instantiate a route analysis
route = arcpy.nax.Route(network_dataset)
注:
安装自定义赋值器时,可以提供特定网络源名称的列表,以指定将要应用自定义赋值器的源。 如果未提供任何列表,则自定义赋值器将应用于所有源。 有关详细信息,请参阅 AttributeEvaluator 文档。
持久化自定义赋值器
持久化自定义赋值器将对自定义赋值器类的引用存储为网络数据集方案的一部分,而该方案存储在地理数据库中。 当使用该网络数据集完成求解时,即会调用这些自定义赋值器。 这些赋值器称为持久化赋值器是因为引用是网络数据集本身的一部分。 它们通过对网络数据集对象使用 updateNetworkDatasetSchema 来配置。
当打开具有持久化自定义赋值器的网络时,将调用 attach 方法,加载并缓存自定义赋值器。 将在应用程序的生命周期内保留此缓存。 这意味着,在关闭并重新启动已打开网络数据集的应用程序之前,将不会读取对持久化自定义赋值器类所做的任何更改。 这适用于 ArcGIS Pro 和 ArcGIS Server。
值得注意的是,对于持久化自定义赋值器,网络数据集方案仅包含对自定义赋值器的引用,不包含类代码。 此引用使得网络数据集在被访问时隐式查找并加载其引用的自定义赋值器。 包含类代码的文件必须位于活动 ArcGIS Pro Python 环境的站点包文件夹中,以便网络数据集可以找到它。
注:
在进行任何修改之前,建议您先克隆默认 ArcGIS Pro Python 环境。 将自定义赋值器 Python 文件添加到站点包目录时,建议您先克隆环境并激活克隆的环境。当您在 Python 脚本外部(例如 ArcGIS Pro 或 ArcGIS Server)执行求解时,如需调用自定义赋值器,则会使用持久化自定义赋值器。
如果要发布为服务的网络分析图层使用具有持久化自定义赋值器的网络数据集,则必须将自定义赋值器包手动复制到 ArcGIS Server Python 环境的站点包目录。 此外,当在服务上使用自定义赋值器时,ArcGIS Server 用户应该可以访问它所使用的任何外部资源(如文件),因为这取决于服务器的配置方式。
配置持久化自定义赋值器
对网络数据集对象使用 updateNetworkDatasetSchema 方法永久更新网络数据集方案,传入用于定义要调用自定义赋值器的网络属性的字典和自定义赋值器类的路径。 该路径使用点标记来定义文件夹名称(在站点包目录中)、类所在文件的文件名以及类名本身。
下例显示了如何更新包含持久化自定义赋值器类的网络数据集。 此示例中的类名为 EdgeCustomizer,其代码位于名为 customization.py 的 Python 模块中,模块所在的文件夹名为 na_customizers,上述内容位于 ArcGIS Pro 活动 Python 环境的站点包文件夹中。
import arcpy
# Create a network dataset object
network_dataset = arcpy.nax.NetworkDataset(
r"C:\Data\Tutorial\SanFrancisco_Persisted.gdb\Transportation\Streets_ND")
# Create a dictionary referencing the custom evaluators to apply to the
# TravelTime attribute
my_custom_evaluators = {
"TravelTime": {"class": "na_customizers.customization.EdgeCustomizer"}
}
# Update the network dataset to use the custom evaluator
network_dataset.updateNetworkDatasetSchema(custom_evaluators=my_custom_evaluators)
注:
使用对自定义赋值器的引用更新方案时,可以提供特定网络源名称的列表,以指定将要应用自定义赋值器的源。 如果未提供任何列表,则自定义赋值器将应用于所有源。 要进行此设置,请将 sourceNames 密钥集包含到源名称列表中。 例如:{
"TravelTime": {
"class": "na_customizers.customization.EdgeCustomizer",
"sourceNames": ["Streets"]
}
}
在网络分析中使用此网络数据集和指定属性时,网络数据集将在求解时调用自定义赋值器。 网络将验证指定的属性和源是否存在,并从活动 ArcGIS Pro Python 环境的站点包文件夹中查找和加载指定的包和类。 如果未找到任何属性、源名称、包或类,则不会使用自定义赋值器。 求解完成时将提示警告消息,报告使用自定义赋值器时出现问题。
当网络数据集包含持久化自定义赋值器时,这些赋值器会在网络数据集属性对话框的常规 > 汇总部分列出。 当查看相关选项卡(例如交通流量属性 > 成本)时,它们也会与引用的网络属性一同显示。 如果加载类时出现问题,则会显示警告消息。
对象生命周期
在最初构建网络数据集时,如果其具有持久化自定义赋值器,则其会实例化在其整个生命周期中引用的自定义赋值器对象,其中生命周期可能因所使用的框架(例如 ArcGIS Pro、ArcGIS Server 或 Python)而有所不同。
由于自定义赋值器对象的特定实例可以在多次求解中使用,因此管理该对象的状态非常重要,特别是要根据需要重置 refresh 方法中的变量。 例如,如果自定义赋值器需要针对每次求解记录边计数,则应在 refresh 方法中重置用于追踪此值的变量。
在 ArcGIS Server 环境中,每个 SOC 进程(在启动时和循环时)将构造新的网络数据集对象,并且还将创建自定义赋值器对象的新实例。 自定义赋值器对象的这个实例将在 SOC 过程的整个生命周期中使用;只有 refresh 方法和 Value 方法会针对每个请求运行。
局限性
以下局限性适用于自定义赋值器:
- 自定义赋值器仅在 ArcGIS Pro 和 ArcGIS Server 中可用。
- 只能在文件或企业级地理数据库上调用自定义赋值器。
- 仅在成本或约束属性引用描述符属性时,才会调用描述符属性。
- 出于性能考虑,自定义赋值器不支持使用关键字作为参数。
创建和使用临时自定义赋值器的快速入门指南
以下部分作为快速指南,将介绍如何创建和使用临时自定义赋值器。 每个代码示例均表示完整工作流中的一个特定组件。 各个组件如下所示:
最后的代码示例显示了如何将所有组件结合到一起。
代码示例是使用 Network Analyst 教程创建的,该教程可从数据下载页面下载。
求解路径分析
下方代码示例演示了使用 arcpy.nax 求解程序对象求解路径分析并检索路径行驶时间的工作流。
注:
下方代码中 gdb 的路径必须更新以反映数据在您的系统中的位置。
import arcpy
# Create a network dataset object
network_dataset = arcpy.nax.NetworkDataset(
r"C:\Data\Tutorial\SanFrancisco.gdb\Transportation\Streets_ND")
# Instantiate a route analysis
route = arcpy.nax.Route(network_dataset)
# Insert stops for the route
with route.insertCursor(
arcpy.nax.RouteInputDataType.Stops,
["NAME", "SHAPE@XY"]
) as cursor:
cursor.insertRow(["Stop1", (-122.501, 37.757)])
cursor.insertRow(["Stop2", (-122.445, 37.767)])
# Solve the route
result = route.solve()
# Print the total travel time for the route
for row in result.searchCursor(
arcpy.nax.RouteOutputDataType.Routes,
["Total_Minutes"]
):
print(f"Solved Total_Minutes: {row[0]}")
定义自定义赋值器类
下面的代码示例演示了自定义赋值器类的定义。 此示例中的自定义赋值器将原始行驶时间成本乘以系数 2。 使用此自定义赋值器求解的路径的行驶时间应为不使用自定义赋值器求解的相同路径的行驶时间的两倍。
class EdgeCustomizer(arcpy.nax.AttributeEvaluator):
"""Defines a custom evaluator that multiplies the edge cost by 2."""
def edgeValue(self, edge: arcpy.nax.Edge):
"""Multiplies the edge cost by 2."""
base_value = self.networkQuery.attributeValue(edge, self.attribute)
return base_value * 2
将自定义赋值器与网络数据集相关联
下面的代码示例演示了如何创建自定义赋值器类实例并将其与网络数据集对象关联,以便用于 TravelTime 成本属性。 此操作应该在调用路径求解之前完成 (route.solve())。
# Create a custom evaluator object that will customize the
# TravelTime cost attribute
travel_time_customizer = EdgeCustomizer("TravelTime")
# Attach the custom evaluator object to the network dataset
network_dataset.customEvaluators = [travel_time_customizer]
合并所有组件
下面的代码示例显示了如何将所有组件一起放入一个完整的工作流中,该工作流用于为路径分析工作流定义并使用临时自定义赋值器。
import arcpy
class EdgeCustomizer(arcpy.nax.AttributeEvaluator):
"""Defines a custom evaluator that multiplies the edge cost by 2."""
def edgeValue(self, edge: arcpy.nax.Edge):
"""Multiplies the edge cost by 2."""
base_value = self.networkQuery.attributeValue(edge, self.attribute)
return base_value * 2
# Create a custom evaluator object that will customize the
# TravelTime cost attribute
travel_time_customizer = EdgeCustomizer("TravelTime")
# Create a network dataset object
network_dataset = arcpy.nax.NetworkDataset(
r"C:\Data\Tutorial\SanFrancisco.gdb\Transportation\Streets_ND")
# Attach the custom evaluator object to the network dataset
network_dataset.customEvaluators = [travel_time_customizer]
# Instantiate a route analysis
route = arcpy.nax.Route(network_dataset)
# Insert stops for the route
with route.insertCursor(
arcpy.nax.RouteInputDataType.Stops,
["NAME", "SHAPE@XY"]
) as cursor:
cursor.insertRow(["Stop1", (-122.501, 37.757)])
cursor.insertRow(["Stop2", (-122.445, 37.767)])
# Solve the route
result = route.solve()
# Print the total travel time for the route
for row in result.searchCursor(
arcpy.nax.RouteOutputDataType.Routes,
["Total_Minutes"]
):
print(f"Solved Total_Minutes: {row[0]}")
创建和使用持久化自定义赋值器的快速入门指南
以下部分作为快速指南,将介绍如何创建和使用持久化自定义赋值器。 在下方内容中,您将在活动 Python 环境中创建一个自定义赋值器类,更新网络数据集以使用自定义赋值器,然后通过求解路径分析测试该赋值器。
代码示例是使用 Network Analyst 教程创建的,该教程可从数据下载页面下载。
克隆默认 Python 环境
自定义赋值器代码必须保存至 ArcGIS Pro Python 环境。 如果使用默认 ArcGIS Python 环境,建议您先克隆并激活新环境。
定义并保存自定义赋值器类
下面的代码示例演示了自定义赋值器类的定义。 此示例中的自定义赋值器将原始行驶时间成本乘以系数 2。 使用此自定义赋值器求解的路径的行驶时间应为不使用自定义赋值器求解的相同路径的行驶时间的两倍。import arcpy
class EdgeCustomizer(arcpy.nax.AttributeEvaluator):
"""Defines a custom evaluator that multiplies the edge cost by 2."""
def edgeValue(self, edge: arcpy.nax.Edge):
"""Multiplies the edge cost by 2."""
base_value = self.networkQuery.attributeValue(edge, self.attribute)
return base_value * 2
在活动的 Python 环境中,找到站点包文件夹。 在该目录内创建一个名为 na_customizers 的文件夹。 将上方用于定义自定义赋值器类的代码保存至 na_customizers 文件夹,命名为 cost_customization.py。
注:
遵循代码示例中使用的示例名称非常重要,因为接下来您将使用这些值更新网络数据集方案。
更新网络数据集方案
将 Network Analyst\Tutorial\SanFrancisco.gdb 从教程数据复制到 SanFrancisco_Persisted.gdb。
在独立脚本中使用下方代码永久更新网络数据集 SanFrancisco_Persisted.gdb,从而为 TravelTime 成本属性引用自定义赋值器。 my_custom_evaluators 字典引用在上方代码示例中定义的自定义赋值器的文件夹名、文件名和类名。
import arcpy
# Create a network dataset object
network_dataset = arcpy.nax.NetworkDataset(
r"C:\Data\Tutorial\SanFrancisco_Persisted.gdb\Transportation\Streets_ND")
# Create a dictionary referencing the custom evaluators to apply to the
# TravelTime attribute
my_custom_evaluators = {
"TravelTime": {"class": "na_customizers.customization.EdgeCustomizer"}
}
# Update the network dataset to use the custom evaluator
network_dataset.updateNetworkDatasetSchema(custom_evaluators=my_custom_evaluators)
求解路径
在 ArcGIS Pro 中,通过 SanFrancisco_Persisted.gdb 中的网络数据集使用驾驶时间出行模式求解路径。 使用 SanFrancisco.gdb 和相同的停靠点求解第二条路径,然后比较输出路径的行驶时间。 引用 SanFrancisco_Persisted.gdb 的路径行驶时间应该是引用 SanFrancisco.gdb 的路径行驶时间的二倍,这是自定义赋值器的效果。