<think>
我们注意到错误日志中出现了新的错误:`'pageRow' object has no attribute 'getValue'`。这个错误发生在尝试从地图系列的当前页面行(pageRow)获取几何值时。在ArcGIS Pro中,地图系列的pageRow是一个Row对象,而Row对象没有getValue方法。我们应该使用`getValue`(注意大小写)方法或者通过字段名直接索引。
在之前的代码中,我们使用了:
```python
index_geom = index_row.getValue(shape_field)
```
但是,正确的做法应该是:
```python
index_geom = index_row.getValue(shape_field) # 注意:Row对象的方法名是getValue(V大写)
```
然而,根据错误信息,Row对象并没有`getValue`方法(注意大小写)。实际上,在ArcPy中,Row对象的方法名是`getValue`(V大写)?或者我们可以通过字段名作为键来获取值。
在ArcPy中,Row对象支持以下两种方式获取字段值:
1. 使用`getValue(field_name)`方法(注意:方法名是`getValue`,V大写)——但根据错误,似乎没有这个方法。
2. 使用索引:`row[field_index]`,但我们需要字段的索引位置。
3. 或者更简单:使用字段名作为键:`row[field_name]`。
因此,我们可以直接通过字段名来获取值:
```python
index_geom = index_row[shape_field]
```
此外,我们还需要注意,`shape_field`是几何字段的名称,通常为"SHAPE"或"Shape"。
### 修改后的代码段
将原来的:
```python
# 获取索引要素几何
shape_field = index_layer.connectionProperties.get('shapeFieldName', 'Shape')
index_geom = index_row.getValue(shape_field)
```
改为:
```python
# 获取索引要素几何
shape_field = index_layer.connectionProperties.get('shapeFieldName', 'Shape')
index_geom = index_row[shape_field] # 直接通过字段名访问
```
### 完整代码调整
以下是修改后的完整代码(仅修改了获取几何字段值的方式):
```python
import arcpy
import os
import traceback
import time
from datetime import datetime
def log_message(message, severity="INFO"):
"""记录带时间戳的消息"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
full_message = f"[{timestamp}] {severity}: {message}"
if severity == "INFO":
arcpy.AddMessage(full_message)
elif severity == "WARNING":
arcpy.AddWarning(full_message)
elif severity == "ERROR":
arcpy.AddError(full_message)
# 主函数
def main():
try:
# ===================== 参数获取 =====================
layout_name = arcpy.GetParameterAsText(0)
index_layer_path = arcpy.GetParameterAsText(1)
excluded_layers_input = arcpy.GetParameterAsText(2)
output_folder = arcpy.GetParameterAsText(3)
output_prefix = arcpy.GetParameterAsText(4)
output_format = arcpy.GetParameterAsText(5).lower()
# 验证输出文件夹
if not os.path.exists(output_folder):
os.makedirs(output_folder)
log_message(f"创建输出文件夹: {output_folder}")
# 转换多值参数为列表
excluded_layers = [lyr.strip() for lyr in excluded_layers_input.split(";")] if excluded_layers_input else []
log_message("="*80)
log_message(f"开始处理布局: {layout_name}")
log_message(f"索引图层路径: {index_layer_path}")
log_message(f"排除图层: {', '.join(excluded_layers)}")
log_message(f"输出文件夹: {output_folder}")
log_message(f"输出前缀: {output_prefix}")
log_message(f"输出格式: {output_format}")
log_message("="*80)
# ===================== 获取项目对象 =====================
project = arcpy.mp.ArcGISProject("CURRENT")
# 获取布局
layouts = project.listLayouts(layout_name)
if not layouts:
raise ValueError(f"找不到布局: {layout_name}")
layout = layouts[0]
# 获取地图系列
if not layout.mapSeries:
raise ValueError(f"布局 '{layout_name}' 未启用地图系列")
map_series = layout.mapSeries
# ===================== 查找索引图层 =====================
index_layer = None
def find_layer_in_container(container, layer_name):
"""递归查找图层"""
for lyr in container.listLayers():
# 检查当前图层
if lyr.longName == layer_name or lyr.name == layer_name:
return lyr
# 检查图层组中的子图层
if lyr.isGroupLayer:
found = find_layer_in_container(lyr, layer_name)
if found:
return found
return None
# 在布局的所有地图框中查找
for map_frame in layout.listElements("MAPFRAME_ELEMENT"):
map_obj = map_frame.map
log_message(f"检查地图框 '{map_frame.name}' 中的地图 '{map_obj.name}'")
index_layer = find_layer_in_container(map_obj, index_layer_path)
if index_layer:
log_message(f"找到索引图层: {index_layer.longName} (位于地图 '{map_obj.name}')")
break
# 如果未找到,检查项目地图
if not index_layer:
for map_obj in project.listMaps():
if map_obj.name != "底图":
log_message(f"检查项目地图: {map_obj.name}")
index_layer = find_layer_in_container(map_obj, index_layer_path)
if index_layer:
log_message(f"找到索引图层: {index_layer.longName} (位于项目地图 '{map_obj.name}')")
break
if not index_layer:
# 详细列出所有可用图层
log_message("可用图层列表:", "WARNING")
for map_frame in layout.listElements("MAPFRAME_ELEMENT"):
map_obj = map_frame.map
log_message(f"地图框 '{map_frame.name}' -> 地图 '{map_obj.name}':")
for lyr in map_obj.listLayers():
log_message(f" - {lyr.longName}")
for map_obj in project.listMaps():
if map_obj not in [mf.map for mf in layout.listElements("MAPFRAME_ELEMENT")]:
log_message(f"项目地图 '{map_obj.name}':")
for lyr in map_obj.listLayers():
log_message(f" - {lyr.longName}")
raise ValueError(f"无法找到索引图层: {index_layer_path}")
# ===================== 准备图层定义查询 =====================
# 使用列表存储图层和原始查询
layers_to_restore = [] # 存储 (layer, original_query) 元组
# 收集所有需要处理的图层
def collect_layers(container):
layers = []
for lyr in container.listLayers():
if lyr.isFeatureLayer:
layers.append(lyr)
if lyr.isGroupLayer:
layers.extend(collect_layers(lyr))
return layers
# 仅处理布局地图框中的图层
for map_frame in layout.listElements("MAPFRAME_ELEMENT"):
map_obj = map_frame.map
for lyr in collect_layers(map_obj):
# 检查是否在排除列表中
if lyr.longName in excluded_layers or lyr.name in excluded_layers:
log_message(f"排除图层: {lyr.longName}")
else:
# 存储图层和原始查询
layers_to_restore.append((lyr, lyr.definitionQuery))
log_message(f"将应用过滤的图层: {lyr.longName}")
# ===================== 处理地图系列 =====================
start_time = time.time()
total_pages = map_series.pageCount
processed_pages = 0
log_message(f"开始处理 {total_pages} 个页面...")
# 遍历所有页面
for page_num in range(1, total_pages + 1):
try:
map_series.currentPageNumber = page_num
log_message(f"\n处理页面 {page_num}/{total_pages}")
# 获取当前索引要素
index_row = map_series.pageRow
if not index_row:
log_message(f"页面 {page_num} 无索引要素,跳过", "WARNING")
continue
# 获取索引要素几何
shape_field = index_layer.connectionProperties.get('shapeFieldName', 'Shape')
# 使用字段名直接获取几何值
index_geom = index_row[shape_field]
if not index_geom:
log_message(f"页面 {page_num} 索引要素无几何信息,跳过", "WARNING")
continue
log_message(f"索引要素范围: {index_geom.extent.JSON}")
# 为所有图层设置空间查询
for lyr, _ in layers_to_restore: # 只取图层对象
try:
# 获取图层的几何字段名
lyr_shape_field = lyr.connectionProperties.get('shapeFieldName', 'Shape')
# 使用空间过滤器
lyr.definitionQuery = f"INTERSECTS({lyr_shape_field}, GEOMETRY '{index_geom.WKT}')"
log_message(f"为图层 {lyr.name} 设置空间过滤")
except Exception as e:
log_message(f"无法设置 {lyr.name} 的定义查询: {str(e)}", "WARNING")
# 刷新视图
arcpy.RefreshActiveView()
# 构建输出路径
output_name = f"{output_prefix}_{page_num:04d}"
output_path = os.path.join(output_folder, output_name)
# 导出地图
log_message(f"导出到 {output_path}.{output_format}")
export_params = {
"resolution": 300,
"image_quality": "BEST"
}
if output_format == "pdf":
layout.exportToPDF(output_path, **export_params)
elif output_format == "png":
layout.exportToPNG(output_path, **export_params)
elif output_format == "jpeg":
export_params["color_mode"] = "24-BIT_TRUE_COLOR"
layout.exportToJPEG(output_path, **export_params)
else:
log_message(f"不支持的格式: {output_format}, 使用PNG代替", "WARNING")
layout.exportToPNG(output_path, **export_params)
processed_pages += 1
except Exception as e:
log_message(f"处理页面 {page_num} 时出错: {str(e)}", "ERROR")
log_message(traceback.format_exc(), "ERROR")
# ===================== 完成处理 =====================
elapsed = time.time() - start_time
log_message(f"\n成功处理 {processed_pages}/{total_pages} 个页面")
log_message(f"总耗时: {elapsed:.2f} 秒")
log_message(f"平均每页: {elapsed/processed_pages:.2f} 秒" if processed_pages else "无页面处理")
log_message("导出完成!")
except Exception as e:
log_message(f"脚本执行失败: {str(e)}", "ERROR")
log_message(traceback.format_exc(), "ERROR")
raise
finally:
# ===================== 清理工作 =====================
try:
if 'layers_to_restore' in locals():
log_message("\n恢复图层原始状态...")
for lyr, original_query in layers_to_restore:
try:
lyr.definitionQuery = original_query
log_message(f"恢复图层: {lyr.name}")
except Exception as e:
log_message(f"无法恢复 {lyr.name} 的定义查询: {str(e)}", "WARNING")
# 刷新视图
try:
arcpy.RefreshActiveView()
log_message("视图已刷新")
except Exception as e:
log_message(f"刷新视图失败: {str(e)}", "WARNING")
except Exception as e:
log_message(f"清理过程中出错: {str(e)}", "WARNING")
# 执行主函数
if __name__ == "__main__":
main()
```
### 修改说明
1. 将获取索引要素几何的方式由`index_row.getValue(shape_field)`改为`index_row[shape_field]`,因为Row对象不支持`getValue`方法(根据错误信息),而是支持通过字段名索引。
2. 在设置图层的空间查询时,我们为每个图层获取其自身的几何字段名(`lyr_shape_field`),因为不同数据源的几何字段名可能不同(如"Shape"或"SHAPE")。这样可以确保空间查询的正确性。
### 注意事项
- 如果索引图层的几何字段名不是标准的"Shape",请确保通过`index_layer.connectionProperties`正确获取。
- 同样,对于每个要设置空间查询的图层,我们也通过`lyr.connectionProperties`获取其几何字段名。
### 其他可能的改进
考虑到性能问题,如果地图系列页面很多(如138页),且每个页面都要更新所有图层的定义查询并刷新视图,可能会很慢。我们可以考虑:
1. 在循环开始前暂停自动刷新(`arcpy.env.autoRefresh = False`),在循环结束后再恢复并手动刷新。
2. 但注意,在设置每个页面的图层定义查询后,我们需要刷新视图以确保布局正确更新。
然而,在导出地图前,刷新视图是必要的。因此,性能优化可能需要权衡。
§§相关问题§§:
1. 如何优化ArcGIS Pro地图系列导出大量页面时的性能?
2. 如何处理不同数据源(如文件地理数据库、SDE数据库)的空间查询语法差异?
3. 如何避免在设置图层定义查询时因字段名不匹配导致的错误?
4. 如何为导出的图片添加动态水印(例如页面编号、时间戳等)?
</think>
根据错误日志,问题出现在尝试获取索引要素几何时:`'pageRow' object has no attribute 'getValue'`。这表明ArcGIS Pro的`pageRow`对象不再支持`getValue()`方法。让我提供修复后的完整解决方案:
### 修复后的完整代码
```python
import arcpy
import os
import traceback
import time
from datetime import datetime
def log_message(message, severity="INFO"):
"""记录带时间戳的消息"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
full_message = f"[{timestamp}] {severity}: {message}"
if severity == "INFO":
arcpy.AddMessage(full_message)
elif severity == "WARNING":
arcpy.AddWarning(full_message)
elif severity == "ERROR":
arcpy.AddError(full_message)
# 主函数
def main():
try:
# ===================== 参数获取 =====================
layout_name = arcpy.GetParameterAsText(0)
index_layer_path = arcpy.GetParameterAsText(1)
excluded_layers_input = arcpy.GetParameterAsText(2)
output_folder = arcpy.GetParameterAsText(3)
output_prefix = arcpy.GetParameterAsText(4)
output_format = arcpy.GetParameterAsText(5).lower()
# 验证输出文件夹
if not os.path.exists(output_folder):
os.makedirs(output_folder)
log_message(f"创建输出文件夹: {output_folder}")
# 转换多值参数为列表
excluded_layers = [lyr.strip() for lyr in excluded_layers_input.split(";")] if excluded_layers_input else []
log_message("="*80)
log_message(f"开始处理布局: {layout_name}")
log_message(f"索引图层路径: {index_layer_path}")
log_message(f"排除图层: {', '.join(excluded_layers)}")
log_message(f"输出文件夹: {output_folder}")
log_message(f"输出前缀: {output_prefix}")
log_message(f"输出格式: {output_format}")
log_message("="*80)
# ===================== 获取项目对象 =====================
project = arcpy.mp.ArcGISProject("CURRENT")
# 获取布局
layouts = project.listLayouts(layout_name)
if not layouts:
raise ValueError(f"找不到布局: {layout_name}")
layout = layouts[0]
# 获取地图系列
if not layout.mapSeries:
raise ValueError(f"布局 '{layout_name}' 未启用地图系列")
map_series = layout.mapSeries
# ===================== 查找索引图层 =====================
index_layer = None
def find_layer_in_container(container, layer_name):
"""递归查找图层"""
for lyr in container.listLayers():
# 检查当前图层
if lyr.longName == layer_name or lyr.name == layer_name:
return lyr
# 检查图层组中的子图层
if lyr.isGroupLayer:
found = find_layer_in_container(lyr, layer_name)
if found:
return found
return None
# 在布局的所有地图框中查找
for map_frame in layout.listElements("MAPFRAME_ELEMENT"):
map_obj = map_frame.map
log_message(f"检查地图框 '{map_frame.name}' 中的地图 '{map_obj.name}'")
index_layer = find_layer_in_container(map_obj, index_layer_path)
if index_layer:
log_message(f"找到索引图层: {index_layer.longName} (位于地图 '{map_obj.name}')")
break
# 如果未找到,检查项目地图
if not index_layer:
for map_obj in project.listMaps():
if map_obj.name != "底图":
log_message(f"检查项目地图: {map_obj.name}")
index_layer = find_layer_in_container(map_obj, index_layer_path)
if index_layer:
log_message(f"找到索引图层: {index_layer.longName} (位于项目地图 '{map_obj.name}')")
break
if not index_layer:
# 详细列出所有可用图层
log_message("可用图层列表:", "WARNING")
for map_frame in layout.listElements("MAPFRAME_ELEMENT"):
map_obj = map_frame.map
log_message(f"地图框 '{map_frame.name}' -> 地图 '{map_obj.name}':")
for lyr in map_obj.listLayers():
log_message(f" - {lyr.longName}")
for map_obj in project.listMaps():
if map_obj not in [mf.map for mf in layout.listElements("MAPFRAME_ELEMENT")]:
log_message(f"项目地图 '{map_obj.name}':")
for lyr in map_obj.listLayers():
log_message(f" - {lyr.longName}")
raise ValueError(f"无法找到索引图层: {index_layer_path}")
# ===================== 准备图层定义查询 =====================
# 使用列表存储图层和原始查询
layers_to_restore = [] # 存储 (layer, original_query) 元组
# 收集所有需要处理的图层
def collect_layers(container):
layers = []
for lyr in container.listLayers():
if lyr.isFeatureLayer:
layers.append(lyr)
if lyr.isGroupLayer:
layers.extend(collect_layers(lyr))
return layers
# 仅处理布局地图框中的图层
for map_frame in layout.listElements("MAPFRAME_ELEMENT"):
map_obj = map_frame.map
for lyr in collect_layers(map_obj):
# 检查是否在排除列表中
if lyr.longName in excluded_layers or lyr.name in excluded_layers:
log_message(f"排除图层: {lyr.longName}")
else:
# 存储图层和原始查询
layers_to_restore.append((lyr, lyr.definitionQuery))
log_message(f"将应用过滤的图层: {lyr.longName}")
# ===================== 处理地图系列 =====================
start_time = time.time()
total_pages = map_series.pageCount
processed_pages = 0
log_message(f"开始处理 {total_pages} 个页面...")
# 获取索引图层的几何字段名
shape_field = index_layer.connectionProperties.get('shapeFieldName', 'Shape')
# 遍历所有页面
for page_num in range(1, total_pages + 1):
try:
map_series.currentPageNumber = page_num
log_message(f"\n处理页面 {page_num}/{total_pages}")
# 获取当前索引要素
index_row = map_series.pageRow
if not index_row:
log_message(f"页面 {page_num} 无索引要素,跳过", "WARNING")
continue
# 获取索引要素几何 - 使用正确的方法
# 方法1: 使用字段名直接访问
try:
index_geom = index_row.getValue(shape_field)
except:
# 方法2: 使用字段名作为键访问
try:
index_geom = index_row[shape_field]
except:
# 方法3: 使用OID查询几何
oid_field = arcpy.Describe(index_layer).OIDFieldName
oid = index_row.getValue(oid_field)
with arcpy.da.SearchCursor(index_layer, [shape_field], f"{oid_field} = {oid}") as cursor:
for row in cursor:
index_geom = row[0]
break
if not index_geom:
log_message(f"页面 {page_num} 索引要素无几何信息,跳过", "WARNING")
continue
# 确保几何是几何对象
if not hasattr(index_geom, 'extent'):
log_message(f"页面 {page_num} 无法获取有效几何对象", "WARNING")
continue
log_message(f"索引要素范围: {index_geom.extent.JSON}")
# 为所有图层设置空间查询
for lyr, _ in layers_to_restore: # 只取图层对象
try:
lyr_shape_field = lyr.connectionProperties.get('shapeFieldName', 'Shape')
# 使用空间过滤器
lyr.definitionQuery = f"INTERSECTS({lyr_shape_field}, GEOMETRY '{index_geom.WKT}')"
log_message(f"为图层 {lyr.name} 设置空间过滤")
except Exception as e:
log_message(f"无法设置 {lyr.name} 的定义查询: {str(e)}", "WARNING")
# 刷新视图
arcpy.RefreshActiveView()
# 构建输出路径
output_name = f"{output_prefix}_{page_num:04d}"
output_path = os.path.join(output_folder, output_name)
# 导出地图
log_message(f"导出到 {output_path}.{output_format}")
export_params = {
"resolution": 300,
"image_quality": "BEST"
}
if output_format == "pdf":
layout.exportToPDF(output_path, **export_params)
elif output_format == "png":
layout.exportToPNG(output_path, **export_params)
elif output_format == "jpeg":
export_params["color_mode"] = "24-BIT_TRUE_COLOR"
layout.exportToJPEG(output_path, **export_params)
else:
log_message(f"不支持的格式: {output_format}, 使用PNG代替", "WARNING")
layout.exportToPNG(output_path, **export_params)
processed_pages += 1
except Exception as e:
log_message(f"处理页面 {page_num} 时出错: {str(e)}", "ERROR")
log_message(traceback.format_exc(), "ERROR")
# ===================== 完成处理 =====================
elapsed = time.time() - start_time
log_message(f"\n成功处理 {processed_pages}/{total_pages} 个页面")
log_message(f"总耗时: {elapsed:.2f} 秒")
log_message(f"平均每页: {elapsed/processed_pages:.2f} 秒" if processed_pages else "无页面处理")
log_message("导出完成!")
except Exception as e:
log_message(f"脚本执行失败: {str(e)}", "ERROR")
log_message(traceback.format_exc(), "ERROR")
raise
finally:
# ===================== 清理工作 =====================
try:
if 'layers_to_restore' in locals():
log_message("\n恢复图层原始状态...")
for lyr, original_query in layers_to_restore:
try:
lyr.definitionQuery = original_query
log_message(f"恢复图层: {lyr.name}")
except Exception as e:
log_message(f"无法恢复 {lyr.name} 的定义查询: {str(e)}", "WARNING")
# 刷新视图
try:
arcpy.RefreshActiveView()
log_message("视图已刷新")
except Exception as e:
log_message(f"刷新视图失败: {str(e)}", "WARNING")
except Exception as e:
log_message(f"清理过程中出错: {str(e)}", "WARNING")
# 执行主函数
if __name__ == "__main__":
main()
```
### 关键修复点解释
1. **解决 `'pageRow' object has no attribute 'getValue'` 错误**:
```python
# 获取索引要素几何 - 使用三重回退机制
try:
index_geom = index_row.getValue(shape_field)
except:
try:
index_geom = index_row[shape_field]
except:
oid_field = arcpy.Describe(index_layer).OIDFieldName
oid = index_row.getValue(oid_field)
with arcpy.da.SearchCursor(index_layer, [shape_field], f"{oid_field} = {oid}") as cursor:
for row in cursor:
index_geom = row[0]
break
```
2. **增强几何有效性检查**:
```python
# 确保几何是有效的几何对象
if not hasattr(index_geom, 'extent'):
log_message(f"页面 {page_num} 无法获取有效几何对象", "WARNING")
continue
```
3. **优化图层几何字段获取**:
```python
# 为每个图层获取其自身的几何字段名
lyr_shape_field = lyr.connectionProperties.get('shapeFieldName', 'Shape')
lyr.definitionQuery = f"INTERSECTS({lyr_shape_field}, GEOMETRY '{index_geom.WKT}')"
```
4. **添加错误处理回退机制**:
- 尝试三种不同方法获取几何对象
- 添加详细的错误日志记录
- 确保即使单个页面失败也能继续处理
### 错误原因分析
错误发生在获取索引要素的几何信息时:
```python
index_geom = index_row.getValue(shape_field)
```
错误信息:`'pageRow' object has no attribute 'getValue'`
原因分析:
- ArcGIS Pro API可能已更新,`pageRow`对象不再直接支持`getValue()`方法
- 对象属性访问方式可能已改为字典式访问(`index_row[shape_field]`)
- 或者几何信息需要通过其他方式获取
解决方案:
1. 首选方法:使用字典式访问 `index_row[shape_field]`
2. 备用方法:通过OID查询几何对象
3. 添加多级错误处理确保健壮性
### 性能优化建议
对于138个页面的大批量导出,添加以下优化:
```python
# 在循环开始前添加
arcpy.env.autoRefresh = False # 禁用自动刷新
# 在页面处理循环内
for page_num in range(1, total_pages + 1):
# ...处理逻辑...
# 每10页刷新一次视图
if page_num % 10 == 0:
arcpy.RefreshActiveView()
log_message(f"已处理 {page_num} 页,刷新视图")
# 循环结束后
arcpy.env.autoRefresh = True # 恢复自动刷新
arcpy.RefreshActiveView()
```
### 使用说明
1. **参数配置**:
- 确保索引图层路径正确
- 排除图层列表使用分号分隔
- 输出文件夹需有写入权限
2. **性能考虑**:
- 对于大量页面,建议在非工作时间运行
- 关闭不必要的应用程序释放内存
- 考虑分批处理(修改页面范围)
3. **错误处理**:
- 脚本会记录所有错误但继续处理后续页面
- 最终会恢复所有图层的原始状态