免费视频|新人指南|投诉删帖|广告合作|地信网APP下载

查看: 3138|回复: 16
收起左侧

Python和ArcGIS自动化制图完全指南(四):自动制图

[复制链接]

4

主题

4529

铜板

5

好友

地信专家组

Rank: 14Rank: 14Rank: 14Rank: 14

积分
183

爱心勋章地信专家组冰雪节勋章

发表于 2021-4-27 14:18 | 显示全部楼层 |阅读模式
本帖最后由 hygnic 于 2021-9-17 10:10 编辑

Python和ArcGIS自动化制图完全指南(四):自动制图

​
前言:在完成了《指南》第二章和第三章后,获得了 PAGESIZE 字段的值,终于来到了最后的制图阶段。运用 Python 和 ArcPy 自动设置 mxd 模板定义查询语句、图框居中、图层样式更新、比例尺的校正、另存 mxd 和各种制图要素的摆放赋值等操作。

​



​

1.限制 MXD 模板


《指南》第三章做的努力在这里得到了显现,在第三章中,我们根据大小、面积等规则给每个制图单位分配了合适的 mxd 模板,其信息就储存在 PAGESIZE 字段中。
每一个制图模板对应的 PAGESIZE  的值就是为其指配的模板大小,或者说名字,毕竟模板用其大小来命名。
如下图所示,巴中市的 PAGESIZE 值是1180x900,表示给巴中市分配的制图模板就是1180x900.mxd。


可能细心的读者已经发现了,到目前为止,仅仅是更新了 MappingIndex 图层及其字段 PAGESIZE 。
但每一个 mxd 模板都是没有限制的,这些模板可以生成所有17个地级市的地图,这显然是不对的。
在这里我们需要对每一个 mxd 模板文件作出限制。
所以我们要对 mxd 模板中的 MappingIndex  图层中设置定义查询语句,从而限制模板,使模板仅生成大小适合的制图单位。

定义查询如下所示,三个模板对应不同的查询语句:


如果还是不太明白,读者可以手动去给 MappingIndex 图层设置定义查询语句,看看会发生什么。

自动设置查询语句的代码 C1 (../main/part2.py)如下:

  1. # -*- coding:utf-8 -*-
  2. # ----------------------------------------------------------
  3. # Author: LiaoChenchen
  4. # Created on: 2021/1/29 17:34
  5. # ----------------------------------------------------------
  6. from __future__ import absolute_import
  7. from __future__ import unicode_literals
  8. from __future__ import print_function
  9. from __future__ import division
  10. import arcpy
  11. import os
  12. ​
  13. ​
  14. """_______global_values_______"""
  15. # 地址
  16. mxd_template = "E:/doc/main/tempMXDS"  # 模板文件位置
  17. output_dir = "E:/doc/main/out"  # 制图输出位置
  18. gdb_path = "E:/doc/main/arcpy指南.gdb"  # 数据库地址
  19. # 重要常量
  20. FIELD = "CITY" # 检索字段
  21. MI = "MappingIndex" # 制图索引文件名称
  22. SCALE = 200000 # 制图的比例尺
  23. """_______global_values_______"""
  24. ​
  25. arcpy.env.overwriteOutput = True
  26. arcpy.env.workspace = gdb_path
  27. ​
  28. sba = arcpy.SelectLayerByAttribute_management
  29. ​
  30. ​
  31. class MakeMXD(object):
  32.    
  33.     def __init__(self, m, lyrs, idx, query_fielt, scale=None):
  34.         """
  35.         :param m: {Object} MXD文件对象
  36.         :param lyrs: {List} 需要设置定义查询语句图层的名称列表
  37.         :param idx: {String} 索引图层名字;MappingIndex
  38.         :param query_fielt: {String} 定义查询使用的字段名;CITY
  39.         :param scale: {Int} 比例尺
  40.         """
  41.         self.mxd = m
  42.         self.df = arcpy.mapping.ListDataFrames(self.mxd)[0]
  43.         self.lyrs = lyrs
  44.         self.idx = idx
  45.         self.field = query_fielt
  46.         self.scale = scale
  47.         
  48.         # MappingIndex
  49.         self.mapidx = arcpy.mapping.ListLayers(self.mxd,self.idx)[0]
  50.         
  51.         self.mapping_index_query()
  52.         self.make_mxd()
  53.         
  54.         del self.mxd
  55.    
  56.     def mapping_index_query(self): # ▶注释1◀
  57.         """
  58.         给 MappingIndex 图层设置定义查询语句;
  59.             PAGESIZE = '1080x700'
  60.         :return:
  61.         """
  62.         map_path = self.mxd.filePath
  63.         # ▶注释2◀
  64.         name = os.path.splitext(os.path.basename(map_path))[0]
  65.         definition_query = ["PAGESIZE"," = ","'",name,"'"]
  66.         self.size = name
  67.         # ▶注释3◀
  68.         self.mapidx.definitionQuery = "".join(definition_query)
复制代码

▶注释1◀:
目前这里我们只需要关注方法 mapping_index_query,上面的初始化方法等拉通梳理一遍后再看。
mapping_index_query 方法用于对 MappingIndex 图层设置定义查询语句。

▶注释2◀:
获取当前模板文件的名称(不包括后缀),赋值给变量 name。

▶注释3◀:
"".join(LIst) 用于拼接字符串,且比使用运算符”+“来拼接字符快。


​

​

2.遍历制图单位


对 mxd 模板文件进行限制处理后,就需要把与该模板匹配的制图单位导出为单独的 mxd 文件出来。
一个 mxd 模板通常对应了几个制图单位。
比如 1080x1300.mxd 这个模板对应着达州市、绵阳市、雅安市三个制图单位。
尽管在第一步中使用定义查询限定了 mxd 模板对应的制图单位,但是一个 mxd 模板通常对应了几个制图单位,比如 1080x1300.mxd 这个大小的模板对应着达州市、绵阳市、雅安市三个制图单位。
所以我们需要遍历 mxd 模板(大白话就是一个一个处理),将其每个对应的制图单位处理出来,而为了制作处理相应制图单位,凸出显示需要的信息、图层、标注等,还要隐藏不需要的其他制图单位的信息。

所以为了完成上述的功能。
会对图层设置定义查询语句以实现图层的显示和隐藏;
设置图层自动居中;
标题、标注的自动更细;
甚至是直接指定或者通过一定规则间接指定各种元素的摆放位置和显示;
...


2.1定义查询


首先,图层中设置定义查询语句的代码 C2 如下:

  1. sba = arcpy.SelectLayerByAttribute_management
  2. ​
  3. class MakeMXD(object):
  4.    
  5.     """
  6.     ...
  7.     """
  8.    
  9.     def make_mxd(self):
  10.         with arcpy.da.SearchCursor(self.mapidx, self.field) as cursor:
  11.             for row in cursor: # 提前解包?
  12.                 name = row[0]
  13.                 self.define_query(name) # 定义查询
  14.                 self.center_scale(name) # 居中
  15.                 self.change_txt(name) # 修改文本
  16.                 self.label_query(name) # 标注查询语句
  17.                 # 取消该图层的所有选择选择项目
  18.                 sba(self.mapidx, "CLEAR_SELECTION")
  19.                 self.saveacopy(name) # 另存
  20.    
  21.     def define_query(self, value):
  22.         """
  23.         定义查询
  24.         :param value: {String/Int/Float} 用于定义查询的值
  25.         :return: None
  26.         """
  27.         for layer in self.lyrs:
  28.             lyr = arcpy.mapping.ListLayers(self.mxd, layer)[0]
  29.             d_q = ['"',self.field,'"'," = ","'",value,"'"]
  30.             lyr.definitionQuery = "".join(d_q)
复制代码

为节约篇幅,方便查看,故将上文出现过的代码省略掉。
倒数第二排的变量 self.field 即字段 CITY;设置的定义查询语句如:CITY = '达州市'


2.2图层居中&设置比例尺


使该制图单位居于布局的中央,同时设置比例尺。

其实现代码 C3 (../main/part2.py)如下:

  1. sba = arcpy.SelectLayerByAttribute_management
  2. ​
  3. ​
  4. class MakeMXD(object):
  5.    
  6.     """
  7.     ...
  8.     """
  9.         
  10.     def center_scale(self, name):
  11.         """
  12.         使图框居中并设置比例尺
  13.         :param name: {String/Int/Float} 用于查询语句的值
  14.         :return: None
  15.         """
  16.         where_clause = "{} = '{}'".format(self.field, name)
  17.         sba(self.mapidx, "NEW_SELECTION", where_clause)
  18.         # ▶注释1◀
  19.         self.df.extent = self.mapidx.getSelectedExtent()
  20.         if self.scale: # ▶注释2◀
  21.             self.df.scale = self.scale
复制代码

▶注释1◀:
df.extent 表示 mxd 文件数据框(dataframe)的范围,通过将制图单位的范围赋予数据框(dataframe),使制图单位居中与数据框中间。

▶注释2◀:
自动化为所有导出的制图单位mxd 添加比例尺。
df.scale 表示数据框的比例尺大小。


2.3修改文本


自动化将原地图标题如:XX市铁路交通分布演示草图 修改成 成都市铁路交通分布演示草图。

代码 C4 (../main/part2.py)如下:

  1. class MakeMXD(object):
  2.    
  3.     """
  4.     ...
  5.     """
  6.         
  7.     def change_txt(self, name):
  8.         # 修改文本
  9.         map_title = "XX市铁路交通分布演示草图"
  10.         for elm in arcpy.mapping.ListLayoutElements(
  11.                 self.mxd, 'TEXT_ELEMENT'):
  12.             if elm.text == map_title:
  13.                 elm.text = map_title.replace("XX市", name)
复制代码

使用 arcpy.mappng.ListLayoutElements 方法将所有的文本元素找出来。
然后选中标题,修改标题文本。


2.4标注查询语句


每一个地级市都设置了标注以显示名称,如果当前制图单位就是该地级市,可以不用再显示该地级市的标注。
如下图的中间的资阳市,就不显示标注:


使特定的区域标注不显示,代码 C5 (../main/part2.py)如下:

  1. class MakeMXD(object):
  2.    
  3.     """
  4.     ...
  5.     """
  6.         
  7.     def label_query(self,name):
  8.         # 设置标注的查询语句
  9.         lyr_label = arcpy.mapping.ListLayers(self.mxd, "市级区域")[0]
  10.         if lyr_label.supports("LABELCLASSES"):
  11.             # NOT( CITY = '巴中市' )
  12.             # ▶注释1◀
  13.             query = ["NOT","( ", self.field, "=", "'", name, "'", " )"]
  14.             for lblClass in lyr_label.labelClasses:
  15.                 lblClass.SQLQuery = "".join(query)
复制代码

▶注释1◀:
查询语句效果:NOT( CITY = '资阳市')
表示不显示资阳市的标注。


2.5.另存 mxd


将在模板上进行的修改保存下来,所以需要将其另存,以制图单位的名称作为文件名。如达州市.mxd、绵阳市.mxd 等。

代码 C6 (../main/part2.py)如下:

  1. class MakeMXD(object):
  2.    
  3.     """
  4.     ...
  5.     """
  6.    
  7.     def saveacopy(self, name):
  8.         # 另存
  9.         self.mxd.saveACopy(output_dir+'/'+name+'.mxd', "10.1")
  10.         print("Complete <name: {} size: {}> ".format(name, self.size))
复制代码


&#8203;

&#8203;

3.完整代码


../main/part2.py 源代码

  1. # -*- coding:utf-8 -*-
  2. # ----------------------------------------------------------
  3. # Author: LiaoChenchen
  4. # Created on: 2021/1/29 17:34
  5. # ----------------------------------------------------------
  6. from __future__ import absolute_import
  7. from __future__ import unicode_literals
  8. from __future__ import print_function
  9. from __future__ import division
  10. import arcpy
  11. import os
  12. &#8203;
  13. &#8203;
  14. """_______global_values_______"""
  15. # 地址
  16. mxd_template = "E:/doc/main/tempMXDS"  # 模板文件位置
  17. output_dir = "E:/doc/main/out"  # 制图输出位置
  18. gdb_path = "E:/doc/main/arcpy指南.gdb"  # 数据库地址
  19. # 重要常量
  20. FIELD = "CITY" # 检索字段
  21. MI = "MappingIndex" # 制图索引文件名称
  22. SCALE = 200000 # 制图的比例尺
  23. """_______global_values_______"""
  24. &#8203;
  25. arcpy.env.overwriteOutput = True
  26. arcpy.env.workspace = gdb_path
  27. &#8203;
  28. sba = arcpy.SelectLayerByAttribute_management
  29. &#8203;
  30. &#8203;
  31. class MakeMXD(object):
  32.    
  33.     def __init__(self, m, lyrs, idx, query_fielt, scale=None):
  34.         """
  35.         :param m: {Object} MXD文件对象
  36.         :param lyrs: {List} 需要设置定义查询语句图层的名称列表
  37.         :param idx: {String} 索引图层名字;MappingIndex
  38.         :param query_fielt: {String} 定义查询使用的字段名;CITY
  39.         :param scale: {Int} 比例尺
  40.         """
  41.         self.mxd = m
  42.         self.df = arcpy.mapping.ListDataFrames(self.mxd)[0]
  43.         self.lyrs = lyrs
  44.         self.idx_name = idx
  45.         self.field = query_fielt
  46.         self.scale = scale
  47.         
  48.         # MappingIndex
  49.         self.mapidx = arcpy.mapping.ListLayers(self.mxd,self.idx)[0]
  50.         
  51.         self.mapping_index_query()
  52.         self.make_mxd() # &#9654;注释1&#9664;
  53.         
  54.         del self.mxd
  55.    
  56.     def mapping_index_query(self):
  57.         """
  58.         给 MappingIndex 图层设置定义查询语句;
  59.             PAGESIZE = '1080x700'
  60.         :return:
  61.         """
  62.         map_path = self.mxd.filePath
  63.         name = os.path.splitext(os.path.basename(map_path))[0]
  64.         definition_query = ["PAGESIZE"," = ","'",name,"'"]
  65.         self.size = name
  66.         self.mapidx.definitionQuery = "".join(definition_query)
  67.    
  68.     def make_mxd(self):
  69.         # &#9654;注释2&#9664;
  70.         with arcpy.da.SearchCursor(self.mapidx, self.field) as cursor:
  71.             for row in cursor: # 提前解包?
  72.                 name = row[0]
  73.                 self.define_query(name) # 定义查询
  74.                 self.center_scale(name) # 居中
  75.                 self.change_txt(name) # 修改文本
  76.                 self.label_query(name) # 标注查询语句
  77.                 # 取消该图层的所有选择选择项目 &#9654;注释3&#9664;
  78.                 sba(self.mapidx, "CLEAR_SELECTION")
  79.                 self.saveacopy(name) # 另存
  80.    
  81.     def define_query(self, value):
  82.         """
  83.         定义查询
  84.         :param value: {String/Int/Float} 用于定义查询的值
  85.         :return: None
  86.         """
  87.         for layer in self.lyrs:
  88.             lyr = arcpy.mapping.ListLayers(self.mxd, layer)[0]
  89.             d_q = ['"',self.field,'"'," = ","'",value,"'"]
  90.             # lyr.definitionQuery = '"' + FIELD + '"' + " = " + "'" + value + "'"
  91.             lyr.definitionQuery = "".join(d_q)
  92.    
  93.     def center_scale(self, name):
  94.         """
  95.         使图框居中并设置比例尺
  96.         :param name: {String/Int/Float} 用于查询语句的值
  97.         :return: None
  98.         """
  99.         where_clause = "{} = '{}'".format(self.field, name)
  100.         sba(self.mapidx, "NEW_SELECTION", where_clause) # &#9654;注释1&#9664;
  101.         self.df.extent = self.mapidx.getSelectedExtent()
  102.         if self.scale:
  103.             self.df.scale = self.scale
  104.    
  105.     def change_txt(self, name):
  106.         # 修改文本
  107.         map_title = "XX市铁路交通分布演示草图"
  108.         for elm in arcpy.mappng.ListLayoutElements(
  109.                 self.mxd, 'TEXT_ELEMENT'):
  110.             if elm.text == map_title:
  111.                 elm.text = map_title.replace("XX市", name)
  112.    
  113.     def label_query(self,name):
  114.         # 设置标注的查询语句
  115.         lyr_label = arcpy.mapping.ListLayers(self.mxd, "市级区域")[0]
  116.         if lyr_label.supports("LABELCLASSES"):
  117.             # NOT( CITY = '巴中市' )
  118.             query = ["NOT","( ", self.field, "=", "'", name, "'", " )"]
  119.             for lblClass in lyr_label.labelClasses:
  120.                 lblClass.SQLQuery = "".join(query)
  121.    
  122.     def saveacopy(self, name):
  123.         # 另存
  124.         self.mxd.saveACopy(output_dir+'/'+name+'.mxd', "10.1")
  125.         print("Complete <name: {} size: {}> ".format(name, self.size))
  126. &#8203;
  127. &#8203;
  128. # 运行窗口
  129. if __name__ == '__main__':
  130.     for a_mxd in [x for x in os.listdir(mxd_template)
  131.                   if ".mxd" or ".MXD" in x]:
  132.         mxd_fullpath = os.path.join(mxd_template, a_mxd)
  133.         mxd = arcpy.mapping.MapDocument(mxd_fullpath)
  134.         MakeMXD(
  135.             mxd,
  136.             ["roads","railways","landuse","natural","buildings"],
  137.             MI, FIELD, SCALE) # &#9654;注释4&#9664;
复制代码

&#9654;注释1&#9664;:
该方法需放到 mapping_index_query 方法的后面,在给各制图单位指定了 mxd 模板后再在进行操作。

&#9654;注释2&#9664;:
遍历一个 mxd 模板中所有可以制作的制图单位。

&#9654;注释3&#9664;:
取消选中。取消居中操作时选中的要素。

&#9654;注释4&#9664;:
该类的第二的参数是一个列表,其中元素是需要进行定义查询语句的图层名称。如:["roads","railways","landuse","natural","buildings"]

&#8203;
Note:输出路径 output_dir 和模板文件位置 mxd_template 尽量使用绝对路径。经测试,使用相对路径会报一些奇怪的错。

&#8203;



&#8203;


5.批量输出图片


  1. <div align="left"><font color="#0100"><font face="Inter"><font style="font-size: 16px"><font color="rgb(51, 51, 51)"><font face="Inter, " "=""><b>part1.py 执行 《指南》第三章的操作。</b></font></font></font></font></font></div><div align="left"><font color="#0100"><font face="Inter"><font style="font-size: 16px"><font color="rgb(51, 51, 51)"><font face="Inter, " "=""><b>part2.py 执行《指南》第四章的操作。</b></font></font></font></font></font></div><div align="left"><font color="#0100"><font face="Inter"><font style="font-size: 16px"><font color="rgb(51, 51, 51)"><font face="Inter, " "=""><b>main.py 文件是将 part1.py 与 part2.py 组合起来,直接执行 main.py 文件即可实现所有的自动化制图工作。</b></font></font></font></font></font></div>
复制代码



运行视频:



&#8203;


部分导出的图片展示:

&#8203;成都市
&#8203;


&#8203;自贡市
&#8203;


&#8203;内江市
&#8203;



&#8203;

&#8203;

5.批量输出图片

关于如何批量导出地图,请查看公众号文章《基于Python的ArcGIS(ArcPy)多进程自动出图》。文章详细介绍了如何使用 Python 代码自动出图,欢迎关注微信公众号查看。


&#8203;

&#8203;

结束语

下载:
  • 演示文件数据
  • 源代码
  • 《指南》文档小册子,便于电脑查看


关注公众号 GIS荟 回复:自动化制图    ,获取所有下载!

&#8203;

&#8203;

&#8203;分享GIS,不止于Python。荟GIS精粹,关注我,带你飞!(长按扫码也行)









4

主题

4529

铜板

5

好友

地信专家组

Rank: 14Rank: 14Rank: 14Rank: 14

积分
183

爱心勋章地信专家组冰雪节勋章

 楼主| 发表于 2021-4-28 09:35 | 显示全部楼层
有什么问题尽管问我哦,如果很急直接微信公众号问我
回复 支持 反对

使用道具 举报

59

主题

2万

铜板

48

好友

资深会员

Rank: 18Rank: 18Rank: 18Rank: 18Rank: 18

积分
3632

灌水勋章

发表于 2021-4-28 11:59 | 显示全部楼层
好高大尚,厉害厉害
回复 支持 反对

使用道具 举报

2

主题

205

铜板

3

好友

助理工程师

Rank: 5Rank: 5

积分
146
发表于 2021-4-28 12:16 | 显示全部楼层
张知识了
回复

使用道具 举报

0

主题

1206

铜板

5

好友

工程师

Rank: 7Rank: 7Rank: 7

积分
529
发表于 2021-4-29 16:05 | 显示全部楼层
互相学习 共同进步
回复 支持 反对

使用道具 举报

0

主题

4197

铜板

5

好友

教授级高工

Rank: 12Rank: 12Rank: 12

积分
1488
发表于 2021-6-27 21:30 | 显示全部楼层
66666666
回复

使用道具 举报

0

主题

1万

铜板

1

好友

教授级高工

Rank: 12Rank: 12Rank: 12

积分
1738
发表于 2021-7-20 08:37 | 显示全部楼层
多谢楼主分享
回复 支持 反对

使用道具 举报

0

主题

1349

铜板

1

好友

助理工程师

Rank: 5Rank: 5

积分
359
发表于 2021-8-24 18:17 | 显示全部楼层
太厉害了。。眼花缭乱
回复 支持 反对

使用道具 举报

2

主题

3万

铜板

6

好友

钻石会员

Rank: 26Rank: 26Rank: 26Rank: 26Rank: 26Rank: 26Rank: 26

积分
5689
发表于 2021-12-19 09:24 | 显示全部楼层
谢谢分享
回复

使用道具 举报

4

主题

1万

铜板

0

好友

钻石会员

Rank: 26Rank: 26Rank: 26Rank: 26Rank: 26Rank: 26Rank: 26

积分
6020
发表于 2022-5-13 08:59 | 显示全部楼层
谢谢分享
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

在线客服
快速回复 返回顶部 返回列表