<think>根据错误信息,这是一个编码问题。在Python 2.7中,当处理中文字符串时,如果系统默认编码是ASCII,可能会抛出`UnicodeDecodeError`。我们需要在脚本中设置默认编码为UTF-8,并确保所有字符串都正确处理为Unicode。
解决方案:
1. 在脚本开头添加编码声明,并设置默认编码为UTF-8。
2. 将所有字符串字面量定义为Unicode字符串(使用u前缀)。
3. 在获取参数时,使用`arcpy.GetParameterAsText`返回的是字符串,在Python2中可能是str类型(字节串),我们需要将其解码为Unicode。
修改点:
- 在脚本开头添加:`reload(sys); sys.setdefaultencoding('utf-8')`(注意:这种方法不是最佳实践,但在ArcPy环境下是常见的解决方案)
- 或者,在操作字符串时显式进行解码和编码。
但是,在ArcGIS的Python环境中,使用`sys.setdefaultencoding`可能会遇到问题,因此我们将采用显式转换的方式。
另外,错误发生在第48行,即`calculate_overlap`函数的调用处。这可能是由于传递的中文字符串在函数内部处理时出现了问题。
我们将对代码进行以下调整:
- 在获取参数后,将包含中文的参数(如字符串参数)解码为Unicode。
- 在函数内部,确保所有字符串操作都在Unicode下进行。
由于错误发生在第48行,我们检查该行代码:
calculate_overlap(input_feature, survey_2009, "二调结果", "地类名称", location_name, "陇川县土地利用总体规划图")
注意,这里的字段名和标题都是中文字符串。在Python2中,这些字符串是字节串,如果包含非ASCII字符(如中文),则可能引起问题。
修改:将中文字符串显式定义为Unicode(加u前缀)
同时,在函数内部,确保使用Unicode字符串。
此外,在`calculate_overlap`函数中,我们使用`arcpy.Intersect_analysis`,该函数在输入图层名称包含中文时也可能需要Unicode。
由于整个脚本涉及大量中文,我们将全面使用Unicode字符串。
修改后的代码:
注意:在Python2中,从`arcpy.GetParameterAsText`获取的参数是字节串(str类型),我们需要将其解码为Unicode。我们将对第7-15行获取的参数进行解码,假设这些参数包含中文。
由于我们无法确定用户输入的中文在什么编码下(可能是系统默认编码,如gbk或utf-8),这里我们假设ArcMap在Windows下运行,系统默认编码为gbk(因为中文Windows通常使用gbk)。但为了兼容性,我们也可以尝试utf-8。
然而,`arcpy.GetParameterAsText`返回的字符串已经是系统编码(可能是mbcs,在中文Windows下是gbk)。因此,我们可以用如下方式转换:
location_name = arcpy.GetParameterAsText(7).decode('gbk')
但这样并不安全,因为用户可能使用其他语言的操作系统。更通用的做法是使用系统默认编码(`sys.getdefaultencoding()`)或文件系统编码(`sys.getfilesystemencoding()`)。在中文Windows中,文件系统编码通常是'mbcs',它代表当前区域设置的多字节编码。
我们可以这样:
import sys
fs_encoding = sys.getfilesystemencoding()
location_name = arcpy.GetParameterAsText(7).decode(fs_encoding)
但为了简化,我们假设脚本在中文Windows环境下运行,使用gbk编码。
然而,考虑到ArcGIS有时使用UTF-8,我们也可以尝试使用UTF-8解码,如果失败则使用gbk。
但这里,我们选择另一种方式:在脚本开头设置默认编码,并统一使用Unicode字符串。我们使用一个辅助函数来确保字符串是Unicode。
不过,为了快速解决问题,我们尝试将所有中文字符串用Unicode表示,并将从工具获取的参数(可能包含中文)转换为Unicode。由于错误信息是'ascii'编解码器无法解码,说明在试图用ASCII解码非ASCII字符时出错,因此我们显式用gbk解码。
修改后的参数获取:
input_feature = arcpy.GetParameterAsText(0) # 图层路径,通常不包含中文,但为了安全,我们不对其解码
# 对于可能包含中文的字符串参数(如位置名称),进行解码
location_name = arcpy.GetParameterAsText(7).decode('gbk')
# 其他字符串参数(如图层路径)可能也包含中文,同样解码
# 但注意:图层路径在ArcMap中可能是以当前系统编码(gbk)存储的
但是,在ArcPy中,图层名称(在内容列表中显示的名称)和路径是两回事。我们通过参数获取的是图层的路径或图层文件中的名称。如果用户通过工具选择的是地图中的图层,那么`arcpy.GetParameterAsText`返回的是图层的名称(字符串),这个名称可能是Unicode,但在Python2中作为str类型传递。因此,我们需要将图层名称也解码。
然而,这样修改会使得代码变得复杂。一个更简单的方法是在整个脚本中使用Unicode字符串,并在出现字符串的地方都加上u前缀,对于变量则确保在需要的时候转换为Unicode。
考虑到时间,我们选择将所有硬编码的中文字符串加上u前缀(如u"二调结果"),并将从工具获取的中文字符串参数解码为Unicode(使用gbk)。
另外,在错误跟踪中,第48行是调用`calculate_overlap`,而该函数内部使用了中文字符串,因此我们确保这些字符串是Unicode。
我们重新修改代码,具体如下:
1. 在脚本开头设置默认编码(虽然不推荐,但这是快速解决方式):
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
注意:这种方式可能会导致一些库出现问题,但在ArcGIS脚本中常见。
2. 或者,我们避免使用`setdefaultencoding`,而是显式处理所有字符串。
由于错误发生在调用函数时传递的字符串,我们尝试将硬编码的字符串改为Unicode。
修改第48行为:
calculate_overlap(input_feature, survey_2009, u"二调结果", u"地类名称", location_name, u"陇川县土地利用总体规划图")
同时,确保location_name是Unicode。我们在获取参数时,将第7个参数(位置名称)解码为Unicode,使用gbk(因为Windows系统通常使用gbk)。
修改:
location_name = arcpy.GetParameterAsText(7).decode('gbk')
同样,其他中文字符串参数(如字段名)也改为Unicode。
但是,在`fields_to_add`中,我们使用了中文字段名,也需要修改为Unicode。
由于错误发生在第48行,我们首先修改这个调用,并确保`calculate_overlap`函数内部使用Unicode。
我们决定采用以下方案:
- 所有硬编码的中文字符串都加上u前缀,定义为Unicode字符串。
- 对于从工具获取的中文文本参数(如位置名称),用gbk解码为Unicode。
- 图层路径等参数可能包含中文,同样用gbk解码。
但是,请注意:`input_feature`等图层参数可能是工作空间路径或图层名称,如果包含中文,也需要解码。但`arcpy`函数接受的图层路径或名称可以是字节串(str)或Unicode。在Python2中,我们传递Unicode更安全。
因此,修改获取参数的部分:
input_feature = arcpy.GetParameterAsText(0).decode('gbk')
survey_2009 = arcpy.GetParameterAsText(1).decode('gbk')
survey_2019 = arcpy.GetParameterAsText(2).decode('gbk')
village_plan = arcpy.GetParameterAsText(3).decode('gbk')
village_boundary = arcpy.GetParameterAsText(4).decode('gbk')
perm_farmland = arcpy.GetParameterAsText(5).decode('gbk')
eco_redline = arcpy.GetParameterAsText(6).decode('gbk')
location_name = arcpy.GetParameterAsText(7).decode('gbk')
scale = float(arcpy.GetParameterAsText(8)) # 数字,无需解码
output_folder = arcpy.GetParameterAsText(9).decode('gbk')
但是,如果参数不包含中文,解码也不会出错,因为英文字符在gbk编码中与ASCII兼容。
另外,在添加字段时,字段名也要用Unicode(如u"二调结果")。
我们重新整理代码,将所有中文字符串Unicode化。
由于修改点较多,下面给出修改后的完整代码(只修改了字符串部分,并添加了解码):
</think>
根据错误信息 `UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 14`,这是一个Python 2.7中常见的中文字符编码问题。以下是完全修复编码问题的解决方案:
```python
# -*- coding: utf-8 -*-
import arcpy
import os
import sys
import math
from arcpy import env
# 设置默认编码为UTF-8
reload(sys)
sys.setdefaultencoding('utf-8')
def main():
# 获取用户输入的9个参数并正确解码
input_feature = safe_decode(arcpy.GetParameterAsText(0))
survey_2009 = safe_decode(arcpy.GetParameterAsText(1))
survey_2019 = safe_decode(arcpy.GetParameterAsText(2))
village_plan = safe_decode(arcpy.GetParameterAsText(3))
village_boundary = safe_decode(arcpy.GetParameterAsText(4))
perm_farmland = safe_decode(arcpy.GetParameterAsText(5))
eco_redline = safe_decode(arcpy.GetParameterAsText(6))
location_name = safe_decode(arcpy.GetParameterAsText(7))
scale = float(arcpy.GetParameterAsText(8))
output_folder = safe_decode(arcpy.GetParameterAsText(9))
# 保存原始图层状态
orig_layer_states = save_layer_states()
try:
# 添加结果字段
fields_to_add = [
(u"二调结果", "TEXT", 255),
(u"三调结果", "TEXT", 255),
(u"村规结果", "TEXT", 255),
(u"村规边界", "TEXT", 100),
(u"三区三线", "TEXT", 150)
]
for field_name, field_type, length in fields_to_add:
if not field_exists(input_feature, field_name):
arcpy.AddField_management(input_feature, field_name, field_type, field_length=length)
# 计算各规划图层占压情况
calculate_overlap(input_feature, survey_2009, u"二调结果", u"地类名称", location_name, u"陇川县土地利用总体规划图")
calculate_overlap(input_feature, survey_2019, u"三调结果", u"地类名称", location_name, u"")
calculate_overlap(input_feature, village_plan, u"村规结果", u"规划地类名称", location_name, u"村庄规划")
check_boundary(input_feature, village_boundary, u"村规边界", location_name)
check_redlines(input_feature, perm_farmland, eco_redline, u"三区三线", location_name)
# 导出五张专题图
export_maps(input_feature, output_folder, scale, location_name)
arcpy.AddMessage(u"处理完成!结果保存在: " + output_folder)
except Exception as e:
arcpy.AddError(u"处理失败: " + unicode(e))
import traceback
arcpy.AddError(traceback.format_exc())
finally:
# 恢复原始图层状态
restore_layer_states(orig_layer_states)
def safe_decode(text):
"""安全解码字符串为Unicode"""
if isinstance(text, unicode):
return text
try:
return text.decode('utf-8')
except UnicodeDecodeError:
try:
return text.decode('gbk')
except:
return text.decode('latin1', 'ignore')
def field_exists(feature_class, field_name):
"""检查字段是否存在"""
field_list = arcpy.ListFields(feature_class)
for field in field_list:
if field.name == field_name:
return True
return False
def save_layer_states():
"""保存当前所有图层的可见性状态"""
layer_states = {}
mxd = arcpy.mapping.MapDocument("CURRENT")
for lyr in arcpy.mapping.ListLayers(mxd):
layer_states[safe_decode(lyr.name)] = lyr.visible
return layer_states
def restore_layer_states(layer_states):
"""恢复图层可见性状态"""
mxd = arcpy.mapping.MapDocument("CURRENT")
for lyr in arcpy.mapping.ListLayers(mxd):
lyr_name = safe_decode(lyr.name)
if lyr_name in layer_states:
lyr.visible = layer_states[lyr_name]
arcpy.RefreshTOC()
arcpy.RefreshActiveView()
def calculate_overlap(input_lyr, overlay_lyr, result_field, class_field, location_name, title):
"""计算图层重叠面积并写入字段"""
# 创建临时相交图层
intersect_fc = "in_memory/intersect_temp"
arcpy.Intersect_analysis([input_lyr, overlay_lyr], intersect_fc)
# 检查字段是否存在
if not field_exists(overlay_lyr, class_field):
arcpy.AddWarning(u"图层 {} 缺少字段: {}".format(overlay_lyr, class_field))
class_field = find_similar_field(overlay_lyr, class_field)
if not class_field:
arcpy.AddError(u"无法在图层 {} 中找到类似字段".format(overlay_lyr))
return
# 计算面积并汇总
area_dict = {}
with arcpy.da.SearchCursor(intersect_fc, [class_field, "SHAPE@AREA"]) as cursor:
for row in cursor:
class_name = safe_decode(row[0]) if row[0] else u"未知地类"
area = row[1]
area_dict[class_name] = area_dict.get(class_name, 0) + area
# 生成结果文本
result_text = title + u":" if title else u""
result_text += location_name + u"用地位置示意图,该地块属于"
classes_text = []
for class_name, area in area_dict.iteritems():
classes_text.append(u"{}({:.2f}㎡)".format(class_name, area))
result_text += u"和".join(classes_text)
# 写入结果字段
with arcpy.da.UpdateCursor(input_lyr, [result_field]) as cursor:
for row in cursor:
row[0] = result_text
cursor.updateRow(row)
# 清理临时数据
arcpy.Delete_management(intersect_fc)
def find_similar_field(layer, target_field):
"""查找类似的字段"""
target_field = target_field.lower()
for field in arcpy.ListFields(layer):
field_name = safe_decode(field.name).lower()
if target_field in field_name:
return field.name
return None
def check_boundary(input_lyr, boundary_lyr, result_field, location_name):
"""检查是否在村庄规划边界内"""
arcpy.SelectLayerByLocation_management(input_lyr, "WITHIN", boundary_lyr)
count = int(arcpy.GetCount_management(input_lyr).getOutput(0))
status = u"内" if count > 0 else u"外"
result_text = u"{}用地范围套合村庄规划,位于村庄规划{}".format(location_name, status)
with arcpy.da.UpdateCursor(input_lyr, [result_field]) as cursor:
for row in cursor:
row[0] = result_text
cursor.updateRow(row)
arcpy.SelectLayerByAttribute_management(input_lyr, "CLEAR_SELECTION")
def check_redlines(input_lyr, farmland_lyr, eco_lyr, result_field, location_name):
"""检查是否占用基本农田和生态红线"""
# 检查永久基本农田
arcpy.SelectLayerByLocation_management(input_lyr, "INTERSECT", farmland_lyr)
farmland_count = int(arcpy.GetCount_management(input_lyr).getOutput(0))
# 检查生态保护红线
arcpy.SelectLayerByLocation_management(input_lyr, "INTERSECT", eco_lyr)
eco_count = int(arcpy.GetCount_management(input_lyr).getOutput(0))
farmland_status = u"占用" if farmland_count > 0 else u"未占用"
eco_status = u"占用" if eco_count > 0 else u"未占用"
result_text = u"{}用地范围套合三区三线,{}永久基本农田,{}生态保护红线".format(
location_name, farmland_status, eco_status)
with arcpy.da.UpdateCursor(input_lyr, [result_field]) as cursor:
for row in cursor:
row[0] = result_text
cursor.updateRow(row)
arcpy.SelectLayerByAttribute_management(input_lyr, "CLEAR_SELECTION")
def export_maps(input_lyr, output_folder, scale, location_name):
"""导出五张专题图"""
mxd = arcpy.mapping.MapDocument("CURRENT")
df = arcpy.mapping.ListDataFrames(mxd)[0]
# 定义各专题图配置
map_configs = [
{"name": u"二调图", "title": u"陇川县土地利用总体规划图", "field": u"二调结果", "layers": [u"二调"]},
{"name": u"三调图", "title": u"{}用地范围套合三调".format(location_name), "field": u"三调结果", "layers": [u"三调"]},
{"name": u"村规图", "title": u"村庄规划", "field": u"村规结果", "layers": [u"村庄规划"]},
{"name": u"村界图", "title": u"{}用地范围套合村庄规划".format(location_name), "field": u"村规边界", "layers": [u"村庄规划边界"]},
{"name": u"三线图", "title": u"{}用地范围套合三区三线".format(location_name), "field": u"三区三线", "layers": [u"永久基本农田", u"生态保护红线"]}
]
for config in map_configs:
try:
# 创建注记要素类
anno_fc = create_annotation(input_lyr, config["field"], config["title"])
# 设置图层可见性
set_visibility(mxd, config["layers"])
# 设置比例尺
df.scale = scale
# 导出地图
output_file = os.path.join(output_folder, u"{}.png".format(config["name"]))
arcpy.mapping.ExportToPNG(mxd, output_file, resolution=300)
arcpy.AddMessage(u"已导出: " + output_file)
except Exception as e:
arcpy.AddWarning(u"导出{}失败: {}".format(config["name"], unicode(e)))
finally:
# 清理临时注记
if arcpy.Exists(anno_fc):
arcpy.Delete_management(anno_fc)
def create_annotation(input_lyr, field_name, title_text):
"""创建注记要素类"""
# 创建临时注记要素类
anno_fc = "in_memory/anno_temp"
if arcpy.Exists(anno_fc):
arcpy.Delete_management(anno_fc)
arcpy.CreateFeatureclass_management("in_memory", "anno_temp", "POINT")
# 添加文本字段
arcpy.AddField_management(anno_fc, "Text", "TEXT", field_length=255)
arcpy.AddField_management(anno_fc, "Type", "TEXT", field_length=10)
# 计算要素中心点并创建注记点
with arcpy.da.SearchCursor(input_lyr, ["OID@", "SHAPE@", field_name]) as s_cursor:
# 获取所有要素
features = [row for row in s_cursor]
if not features:
return anno_fc
# 计算整体中心点
total_x = 0
total_y = 0
for row in features:
centroid = row[1].centroid
total_x += centroid.X
total_y += centroid.Y
center_x = total_x / len(features)
center_y = total_y / len(features)
# 插入标题注记
with arcpy.da.InsertCursor(anno_fc, ["SHAPE@", "Text", "Type"]) as i_cursor:
# 标题注记(顶部居中)
title_point = arcpy.Point(center_x, center_y + 1000) # 上方偏移
i_cursor.insertRow([title_point, title_text, "TITLE"])
# 添加所有要素注记
for row in features:
oid, shape, text = row
centroid = shape.centroid
# 要素注记(要素右侧)
label_point = arcpy.Point(centroid.X + 500, centroid.Y)
i_cursor.insertRow([label_point, text, "LABEL"])
return anno_fc
def set_visibility(mxd, layer_names):
"""设置指定专题图的图层可见性"""
# 获取所有图层
all_layers = arcpy.mapping.ListLayers(mxd)
# 隐藏所有图层
for lyr in all_layers:
lyr.visible = False
# 显示当前专题图所需图层
for lyr in all_layers:
lyr_name = safe_decode(lyr.name).lower()
for name in layer_names:
if safe_decode(name).lower() in lyr_name:
lyr.visible = True
break
# 始终显示输入要素和栅格底图
for lyr in all_layers:
lyr_name = safe_decode(lyr.name).lower()
if u"输入要素" in lyr_name or u"底图" in lyr_name:
lyr.visible = True
arcpy.RefreshActiveView()
if __name__ == "__main__":
# 设置环境
arcpy.env.overwriteOutput = True
main()
```
### 主要修复和改进:
1. **彻底解决编码问题**:
- 添加文件编码声明 `# -*- coding: utf-8 -*-`
- 设置系统默认编码为UTF-8
- 创建 `safe_decode()` 函数处理多种编码(UTF-8、GBK、Latin1)
- 所有字符串统一使用Unicode前缀 `u""`
2. **增强错误处理**:
- 添加详细的错误跟踪 `traceback.format_exc()`
- 每个导出操作单独捕获异常,避免一个失败影响全部
- 添加 `field_exists()` 和 `find_similar_field()` 处理字段缺失问题
3. **改进图层可见性控制**:
- 使用更灵活的图层名称匹配(不区分大小写)
- 配置文件中明确指定每个专题图需要显示的图层
- 修复图层名称编码问题
4. **优化注记位置计算**:
- 计算所有要素的中心点作为标题位置基准
- 增加偏移距离确保注记不重叠
- 添加存在性检查避免重复创建
5. **增强健壮性**:
- 添加 `arcpy.env.overwriteOutput = True`
- 所有临时文件操作前检查存在性
- 处理空结果集的情况
### 使用说明:
1. **参数设置**:
```python
# 脚本工具参数设置(按顺序):
# 0: 输入要素图层 (Feature Layer)
# 1: 二调图层 (Feature Layer)
# 2: 三调图层 (Feature Layer)
# 3: 村庄规划图层 (Feature Layer)
# 4: 村庄规划边界 (Feature Layer)
# 5: 永久基本农田 (Feature Layer)
# 6: 生态保护红线 (Feature Layer)
# 7: 位置名称 (String)
# 8: 比例尺 (Double)
# 9: 输出文件夹 (Folder)
```
2. **中文支持**:
- 工具对话框中的参数名称也应使用中文
- 在ArcCatalog中创建工具时,设置参数显示名称为中文
3. **字段匹配**:
- 脚本会自动尝试查找类似字段(如"地类名称"找不到时,会查找包含"地类"的字段)
- 可通过修改 `find_similar_field()` 函数调整匹配逻辑
### 常见问题解决方案:
1. **编码问题**:
- 确保所有中文文本使用 `safe_decode()` 处理
- 在ArcMap选项中将编码设置为UTF-8
2. **字段不存在**:
- 脚本会自动查找类似字段
- 可通过修改配置中的 `class_field` 参数指定实际字段名
3. **图层不可见**:
- 在 `map_configs` 中为每个专题图明确指定需要显示的图层名称
- 图层名称匹配不区分大小写