歐丁丁,張 琪,劉世好,曹 虹,文 汲,周 維,尹祺卿,胡春香
(1.國家林業(yè)和草原局中南調(diào)查規(guī)劃設計院,長沙 410014; 2.中南林業(yè)科技大學,長沙 410004)
林業(yè)制圖是林業(yè)工作中的一項基礎性工作,也是向大眾直觀展示林業(yè)調(diào)查、規(guī)劃設計成果的重要途徑[1-3],但傳統(tǒng)的林業(yè)制圖工作量大、技術要求高[4-7],特別具體到每個小班的現(xiàn)狀和設計情況的圖件制作,圖件數(shù)量多,制圖時間周期長,嚴重擠占項目時間。基于此,部分學者開始思考如何進行自動出圖,并尋求適合特定條件下的自動出圖方法。有的學者對單個小班的出圖方法進行了研究,如:李霄等[8]基于Arcgis內(nèi)置站點包ArcPy對違法礦山小班的現(xiàn)狀進行自動出圖方法研究,通過建立地圖模板,遍歷要素的每一個小班,使用動態(tài)文本對小班現(xiàn)狀進行描述,最后導出圖片,相比人工制圖節(jié)省了大量的時間;陽昭[9]利用ArcGIS10.2的數(shù)據(jù)驅動頁面,對低效林改造作業(yè)設計項目小班進行了自動分幅出圖,實現(xiàn)了林業(yè)項目專題圖的自動分幅出圖。雖然已有不少有關ArcGIS自動出圖的研究,但目前依然存在尚未解決的問題,如:在對多個小班自動出圖的時候,如何避免圖面框線與小班界線相交的問題、如何在出圖時自動生成小班屬性簡表,以及同時一張已定紙張大小的范圍能內(nèi)包含最多的小班,從而節(jié)約紙張并且保持出圖界面的美觀等。本研究將通過結合ArcGIS內(nèi)置站點包ArcPy和基于Excel的編程語言VBA解決上述問題,為林業(yè)自動化制圖提供一種新的方法。
長順縣位于貴州省中南部,地處烏蒙山—苗嶺生態(tài)屏障中段及烏江生態(tài)保護帶中段,距省城貴陽市 87 km,據(jù)黔南自治州首府都勻市178 km,北靠貴陽市及平壩縣,東鄰長順縣,東南抵羅甸縣,西南抵紫云苗族布依族自治縣,西北抵安順,東北抵貴陽市花溪;屬濕潤地區(qū)的亞熱帶季風氣候,具有山岳型氣候特點,冬春干燥,夏季濕潤,四季分明。
近年來,長順縣積極推進國家儲備林建設進度,已完成國家儲備林建設一期項目可研,為全面完成貴州省國家儲備林建設任務,科學經(jīng)營和精準提升長順縣森林質(zhì)量,增強區(qū)域內(nèi)優(yōu)質(zhì)木材和生態(tài)產(chǎn)品的供應能力,鞏固全縣脫貧攻堅成果,深入論證長順縣國家儲備林二期建設的必要性和可行性,長順縣林業(yè)局委托國家林業(yè)和草原局中南調(diào)查規(guī)劃設計院編制《長順縣國家儲備林二期建設項目可行性研究報告》。
數(shù)據(jù)來源于《長順縣國家儲備林二期建設項目可行性研究報告》中劃定的國家儲備林建設小班。
1)ArcPy簡介
ArcPy是一個以成功的 arcgisscripting 模塊為基礎并繼承了 arcgisscripting 功能進而構建而成的站點包。目的是為以實用高效的方式通過 Python 執(zhí)行地理數(shù)據(jù)分析、數(shù)據(jù)轉換、數(shù)據(jù)管理和地圖自動化創(chuàng)建基礎。在ArcGIS的內(nèi)置Python窗口中可以導入AArcPy站點包,進行數(shù)據(jù)的處理和分析。
使用數(shù)據(jù)驅動框架進行小班自動出圖的關鍵是小班索引圖層的創(chuàng)建,而ArcGIS中自帶的索引圖層創(chuàng)建功能難以滿足出圖要求,如按鄉(xiāng)鎮(zhèn)出圖、邊框與小班界不相交等。本研究將使用ArcPy進行小班索引圖層的創(chuàng)建。
2)VBA簡介
VBA(Visual Basic for Applications)是微軟公司基于VB(Visual Basic)和office開發(fā)的自動化辦公語言,VBA與VB編程語言的編程語法、開發(fā)機制和語言結構幾乎一致,主要能用來擴展office的應用程序功能。
小班屬性是小班設計圖的重要組成部分,用于補充每個小班的屬性及設計情況,通??梢杂脛討B(tài)文本和小班簡表的方式實現(xiàn)。對于一個小班的小班設計圖而言,動態(tài)文本的實現(xiàn)方式相對簡單,文本布局可根據(jù)需求自行排版,但是不能同時顯示多個小班的屬性信息。使用小班簡表可同時顯示多個小班的屬性信息,但是對于自動出圖而言,如何根據(jù)小班屬性表桉小班索引圖層定制的分幅自動生成小班簡表是需要解決的重點及難點。本研究基于Excel使用VBA編程語言自動生成小班簡表。
1)圖層的添加。根據(jù)國家儲備林相關技術規(guī)程,使用地形圖作為底圖,行政界線包括鄉(xiāng)鎮(zhèn)界和村界,在ArcMAP中依次添加地形圖、鄉(xiāng)鎮(zhèn)界線、村界線、設計小班和索引圖層。其中:設計小班和索引圖層可為任意面圖層,并將其圖層名稱分別更改為XB_JX和qdfz,同時為qdfz添加tplj字段和page字段。
2)數(shù)據(jù)驅動頁面設置。在設置數(shù)據(jù)驅動頁面中,勾選啟用數(shù)據(jù)驅動頁面數(shù)據(jù)框為默認圖層,索引圖層為qdfz圖層,名稱字段為page,排序字段為OBJECTID字段,調(diào)整當前比例為1∶10000,然后在范圍中選擇居中并保持當前比例。
3)頁面設置。轉為布局視圖,并在文件—頁面和打印設置中將紙張設置為A3橫向,將數(shù)據(jù)框調(diào)整為合適大小,添加公里格網(wǎng),再依次添加指北針、圖例和比例尺,然后插入圖片,將圖片錨點設置為左上定點,并能進行移動和縮放,保證圖片左上頂點與數(shù)據(jù)框頂點重合,圖片大小以100%縮放時能看清圖片中的文字為宜,然后右擊圖片查看屬性,在源中選擇數(shù)據(jù)驅動頁面的簡單路徑,索引圖層的字段設置為tplj。
然后為小班設計圖添加動態(tài)標題,在插入中選擇插入標題,代碼如圖1所示。
圖1 小班設計圖標題動態(tài)文本
4)頁面定義查詢。右擊XB_JX,在定義查詢選擇頁面定義,啟用頁面定義查詢頁面名稱字段選擇page,選擇匹配。這樣可保證在出圖時,XB_JX圖層只顯示與數(shù)據(jù)驅動頁面中page字段屬性一樣的小班。
5)小班標注。利用小班標注可以將小班界與屬性信息相關聯(lián),本研究使用分子式進行小班信息的標注,其中:[bz]為小班號字段。如圖2所示。
圖2 標注樣式
6)圖層范圍指示器。圖層范圍指示器可用于標識出圖范圍在總范圍中的位置,本研究中還可用來檢驗出圖效果(通過查看小班界線與指示范圍是否相交來檢驗)。實現(xiàn)步驟為:插入數(shù)據(jù)框、添加小班數(shù)據(jù)、添加鄉(xiāng)鎮(zhèn)界、更改數(shù)據(jù)顯示樣式、右擊新建的圖層選擇范圍指示器選中圖層并勾選使用簡單范圍。
設置完后將出圖模板保存?zhèn)溆谩?/p>
小班索引圖層創(chuàng)建的關鍵是在1∶10000的比例尺下,將完全被A3紙覆蓋的小班的page字段命名為相同的值。
1)獲取屬性表。在ArcPy中對小班的屬性進行查詢分析有兩種方法:一種是使用游標依次進行遍歷,另一種是將屬性表導出為數(shù)組進行操作,本步驟只能選擇第一種方式進行,所需要的小班屬性包括小班坐標點、OBJECTID、page字段和鄉(xiāng)鎮(zhèn)字段,在使用游標遍歷小班并保存所需的屬性信息至數(shù)組后,對數(shù)組按照鄉(xiāng)鎮(zhèn)名稱進行排序,方便后面按鄉(xiāng)鎮(zhèn)進行位置距離的搜索。實現(xiàn)代碼如圖3所示。
圖3 獲取屬性表代碼
2)賦值屬性表。將所需要的屬性提取出來后,要對page字段進行賦值,對屬性表中的每一行,提取每個小班的四至坐標,如果鄉(xiāng)鎮(zhèn)名字相同,則進行遍歷,遍歷分兩次進行,第一層是遍歷每一個小班,第二層是針對這一個小班對比鄉(xiāng)鎮(zhèn)內(nèi)其他小班的四至坐標與本小班的四至坐標的x方向實際距離的最大值和y方向上實際距離的最大值,如果此距離均小于A3紙張的實際距離的某個比例(因為小班范圍不會鋪滿整張A3紙),則將其page字段賦相同的值,同時,進行檢測,若添加了一個新的小班后,整體的范圍超過了A3紙張的實際距離的某個比例,則不賦同樣的值。實現(xiàn)代碼如下:
def fz_sxb(sxb_list,cs):
i =0
ss_list=[]
xz_list=[]
for shp in sxb_list:
x1=[]
y1=[]
if shp[2] is None:
i+=1
x_z=[]
y_z=[]
if shp[3] not in xz_list:
i=1
xz_list.append(shp[3])
for plts in shp[0]:
x1.append(plts[0])
y1.append(plts[1])
shp[2]=i
x_z=x1
y_z=y1
for row1 in sxb_list:
x2=[]
y2=[]
if row1[2] is None and row1[3]==shp[3]:
for pltss in row1[0]:
x2.append(pltss[0])
y2.append(pltss[1])
x_z=x_z+x2
y_z=y_z+y2
x_min=min(x_z)
x_max=max(x_z)
y_min=min(y_z)
y_max=max(y_z)
x_jl=x_max-x_min
y_jl=y_max-y_min
if x_jl<=(4200*float(cs)) and y_jl <=(2970*float(cs)):
row1[2]=i
else:
for x in x2:
x_z.remove(x)
for y in y2:
y_z.remove(y)
ss_list.append((shp[1],shp[2]))
return ss_list
CS表示出圖范圍相對A3紙的比例。
3)賦值字段。進行屬性表(數(shù)組)賦值后,需將結果賦值給小班屬性表的字段,本步驟使用游標進行賦值。實現(xiàn)代碼如下:
def fz_zd(fc,field,xzfield,cs):
k_list=fz_sxb(hq_sxb(fc,field,xzfield),cs)
with arcpy.da.UpdateCursor(fc,["OID@",field,xzfield])as cursor:
for row in cursor:
for k in k_list:
if row[0]==k[0]:
row[1]=str(row[2])+"_"+str(k[1])
cursor.updateRow(row)
完成上述步驟后,將字段賦值后的小班按照page字段進行小班融合,生成數(shù)據(jù)驅動頁面的索引圖層。本研究將此步驟放到自動出圖方法中實現(xiàn)(3.4節(jié))。
1)轉為Excel。將賦值字段后的小班屬性表,使用轉換工具箱中的表轉Excel轉成Excel。
2)數(shù)據(jù)清理與格式設置。導出的屬性表中,包含了一些不需要顯示在小班簡表中的信息,需手動進行刪除,同時,行列的寬度和長度、字體格式和大小等需手動進行調(diào)節(jié)。最后將page字段移動至最后一列方便圖片保存。
3)使用VBA自動生成小班簡表圖片。首先生成page字段的唯一值字典,然后對page字段進行篩選,把篩選的內(nèi)容復制成圖片然后新建空白的圖表,把內(nèi)容粘貼進圖表,再使用VBA圖表的導出功能可將內(nèi)容轉換成圖片。實現(xiàn)代碼如下:
Sub xbjb()
Dim arr()
Dim dic
Dim pa
Dim a As Range
Dim i, j As Integer
Dim arr2()
Dim shp As Shape
Dim oChartObject As ChartObject
Dim oChart As Chart
sPath = Excel.ThisWorkbook.Path & "”
arr = Sheet2.Range("a3:" & Chr(Range("z3").End(xlToLeft).Column + 64) & Range("a65536").End(xlUp).Row)
Set dic = CreateObject("scripting.dictionary")
pa = Chr(Range("z2").End(xlToLeft).Column + 64)
arr2 = Range(pa & "3:" & pa & Range(pa & "65536").End(xlUp).Row)
For i = LBound(arr2) To UBound(arr2)
dic(arr2(i, 1)) = 1
Next
Rows("2:2").Select
Range("F2").Activate
Selection.AutoFilter
For Each d In dic.keys
ActiveSheet.Range("$A$2:" & "$" & pa & "$" & Range("a65536").End(xlUp).Row).AutoFilter Field:=Range("z3").End(xlToLeft).Column, Criteria1:=d
Range("A1:" & Chr(Range("z3").End(xlToLeft).Column + 63) & Range("a65536").End(xlUp).Row).CopyPicture Appearance:=xlScreen, Format:=xlPicture
ActiveSheet.Paste
For Each shp In Shapes
shp.Title = d
shp.Copy
Set oChartObject = ChartObjects.Add(0, 0, shp.Width, shp.Height)
oChartObject.Activate
ActiveChart.Paste
oChartObject.Chart.Export sPath & d & ".jpg"
shp.Delete
For Each oChartObject In ChartObjects
oChartObject.Delete
Next
Next
Next
End Sub
用ArcPy的融合代碼生成索引圖層,并為索引圖層的tplj和xh字段賦值,tplj表示小班屬性表所對應的絕對路徑,xh表示該張小班設計圖的序號。根據(jù)之前制作的出圖模板,替換XB_JX及qdfz的數(shù)據(jù)源,最后結合數(shù)據(jù)驅動導出圖片,保存為jpg格式。出圖代碼如下:
import arcpy
import os
fc=arcpy.GetParameterAsText(0)
mxd=arcpy.mapping.MapDocument((arcpy.GetParameterAsText(1)).decode("utf-8"))
excel_sjqd=arcpy.GetParameterAsText(2)
tp_luj=arcpy.GetParameterAsText(3)
tp_fbl=int(arcpy.GetParameterAsText(4))
wj_lj=os.path.dirname(mxd.filePath)
if os.path.exists(os.path.join(wj_lj,"sjqd_ys.gdb")):
arcpy.Delete_management(os.path.join(wj_lj,"sjqd_ys.gdb"))
arcpy.CreateFileGDB_management(wj_lj,"sjqd_ys")
else:
arcpy.CreateFileGDB_management(wj_lj,"sjqd_ys")
out_gdb=os.path.join(wj_lj,"sjqd_ys.gdb")
arcpy.Dissolve_management(fc, os.path.join(out_gdb,"XB_RH"),
"Page", "", "MULTI_PART",
"DISSOLVE_LINES")
arcpy.AddField_management(os.path.join(out_gdb,"XB_RH"),"xz","TEXT")
arcpy.AddField_management(os.path.join(out_gdb,"XB_RH"),"xh","TEXT")
arcpy.AddField_management(os.path.join(out_gdb,"XB_RH"),"tplj","TEXT")
with
arcpy.da.UpdateCursor(os.path.join(out_gdb,"XB_RH"),["Page","xz","xh","tplj"])as cursor:
for row in cursor:
row[1]=str(row[0]).split("_")[0]
row[2]=str(row[0]).split("_")[1]
row[3]=str(os.path.join(excel_sjqd,row[0]))+".jpg"
cursor.updateRow(row)
arcpy.Copy_management(fc,os.path.join(out_gdb,"XB_FZ"))
df=arcpy.mapping.ListDataFrames(mxd)[0]
lyr_list=arcpy.mapping.ListLayers(df)
for lyr in lyr_list:
if lyr.name == "XB_FZ" or lyr.name == "XB_RH":
arcpy.mapping.RemoveLayer(df,lyr)
if lyr.name == "qdfz":
lyr.replaceDataSource(out_gdb,"FILEGDB_WORKSPACE","XB_RH")
if lyr.name =="XB_JX":
lyr.replaceDataSource(out_gdb,"FILEGDB_WORKSPACE","XB_FZ")
if lyr.name ==os.path.basename(fc):
arcpy.mapping.RemoveLayer(df,lyr)
arcpy.RefreshActiveView
for pageNum in range(1, mxd.dataDrivenPages.pageCount + 1):
mxd.dataDrivenPages.currentPageID = pageNum
filds=["OID@","xz","xh"]
with arcpy.da.SearchCursor(os.path.join(out_gdb,"XB_RH"),filds) as cursor:
for row in cursor:
if str(row[0])==str(pageNum):
a=str(str(row[1])+"小班設計圖"+str(row[2])+".jpg").decode("utf-8")
arcpy.mapping.ExportToJPEG(mxd,os.path.join(tp_luj,a),resolution=tp_fbl)
del mxd
自動化制圖的代碼比較復雜,進行成果分享時需對代碼進行包裝,本研究使用ArcGis的腳本制作工具進行代碼包裝,在ArcGis的目錄中新建工具箱,并新建Python腳本工具,由前文步驟可知,共需新建2個Python腳本,第一個為小班字段賦值腳本,第二個為自動出圖腳本。結果如圖4、圖5所示。
圖4 字段賦值腳本
圖5 自動出圖腳本
小班設計圖全部導出完成后,對小班設計圖進行手動檢查,檢查小班界是否與圖框相交、是否同時出現(xiàn)兩個鄉(xiāng)鎮(zhèn)的小班等不符合要求的小班設計圖。
經(jīng)過檢查后, 121張小班設計圖,只有1張小班設計圖由于小班簡表與小班界線存在相交的現(xiàn)象,美觀率99.2%。在具體操作中,不合格小班設計圖可手動進行拖動重新出圖,或者調(diào)整參數(shù)的大小或者調(diào)整頁面元素的布局,從而保證小班設計圖與小班簡表不重疊。
從出圖結果看,所有小班設計圖的小班界均與圖框不相交,不僅實現(xiàn)了按照鄉(xiāng)鎮(zhèn)出圖,還確保了圖面上小班數(shù)量的最大化,在節(jié)約出圖時間的同時保證紙張數(shù)量最少。如圖6所示。
根據(jù)小班的實際位置與實際A3紙張的大小確定各小班所在的頁碼,在保證小班界線與框線不相交的同時,確保一張A3頁面中的小班個數(shù)最大化,從而減少了紙張數(shù)量。但頁面元素中,小班簡表的位置相對不固定,小班個數(shù)越多的頁面,其小班簡表所占用的范圍也越大,與小班簡表交叉的可能性也越高,通過設置出圖相對比例參數(shù)可降低交叉的可能性。
圖6 小班設計圖
本研究基于ArcGis內(nèi)置站點包ArcPy和VBA,從圖件美觀和減少紙張數(shù)的角度出發(fā),將自動化制圖分解為四個步驟,使用VBA進行索引圖層的制作和自動出圖、使用VBA進行小班屬性簡表的自動生成,較好地解決了林業(yè)專題圖中多個小班自動出圖所面臨的一些問題,實現(xiàn)了林業(yè)制圖的自動化和美觀化,在提高工作效率的同時,不僅確保了所有圖件的統(tǒng)一性,還保證了圖件的美觀。
1)使用傳統(tǒng)的手工制作林業(yè)專題圖,需要重復的進行小班簡表的插入或手動更改圖面文字信息、重復地導出地圖,不僅過程繁瑣,還容易出錯,工作效率極低。本研究通過結合ArcPy和VBA,實現(xiàn)了林業(yè)專題圖的自動出圖,在保證圖件美觀的同時大大提升了工作效率。
2)在以往關于自動出圖的研究中,大都是使用原圖層作為索引圖層,故一個圖件上只能出一個小班,本研究使用ArcPy進行索引圖層的制作,通過小班位置距離搜索功能,自動調(diào)節(jié)圖上的小班范圍比例,實現(xiàn)了多個小班自動出圖。
3)與添加動態(tài)文本作為小班屬性相比,本研究使用VBA生成小班屬性簡表作為小班屬性信息,可以添加多個小班的任意屬性信息,同時還能使用Excel對小班簡表進行風格化處理,滿足不同的需求。
本研究開發(fā)設計的自動出圖工具對于提高林業(yè)制圖工作效率具有很重要的現(xiàn)實意義,結合ArcPy和VBA,解決了以往自動出圖中添加小班屬性信息困難、多個小班出圖繁瑣等問題,能夠節(jié)省大量時間用于項目成果質(zhì)量的把控,具有很好的推廣意義。但本研究依然存在不足,如小班簡表的制作過程中,小班個數(shù)越多則小班簡表的長度越長,與小班界相交的可能性越大,在設計代碼的過程中由于無法提前知曉每張小班設計圖中小班的個數(shù)和分布情況,故暫時未能從源頭解決問題,只能調(diào)節(jié)頁面布局情況和相關參數(shù)大小,在今后的研究中需進一步進行優(yōu)化。