利用Arcpy釋出地圖服務,製作切片
因為工作需求,我需要把管理員上傳的圖片進行切片,供使用者下載切片離線瀏覽。後臺切片功能主要分為以下幾個步驟:
1. 製作地圖文件(*.mxd);
2. 釋出地圖文件;
3. 製作伺服器快取;
4. 生成切片;
5. 打包成zip
ArcGIS版本是10.2,下面這些程式碼應該在10.0上無法執行。生成mxd文件時不支援柵格資料,我的圖片大都是jpeg格式的,所以最終我更換了ArcGIS版本,妥協了…
Arcpy製作地圖文件
我沒有找打直接生成地圖文件的示例程式碼,因此我使用了SaveAs的方法,就是利用已有的空白文件(我稱之為“模板”),載入上jpg圖片後再“另存為”。這樣的一個好處就是我可以直接在模板中定義地理座標系,後面再發布為地圖服務時會用到的,否則報錯。
# 建立map document
def CreateMxd(imagepath,mxdpath):
dirname=os.path.dirname(imagepath)
imagename=os.path.basename(imagepath)
dotindex=imagename.index('.')
name=imagename[0:dotindex]
new_mxd=os.path.abspath(dirname+"/"+name+".mxd")
rasterLayer="raster"
temp_mxd = arcpy.mapping.MapDocument(mxdpath)
df=arcpy.mapping.ListDataFrames(temp_mxd,"Layers")[0]
arcpy.MakeRasterLayer_management(imagepath,rasterLayer,"","","")
addLayer=arcpy.mapping.Layer(rasterLayer)
arcpy.mapping.AddLayer(df,addLayer,"TOP")
temp_mxd.saveACopy (new_mxd)
del temp_mxd
return new_mxd
這個函式的引數是“圖片的位置”和“模板的位置”,然後將圖片的名稱提取出來作為新mxd檔案的名稱。
需要注意的是,直接新增jpg格式的圖層在ArcGIS10.2中是允許的,但生成的mxd文件裡面的圖層樣式是採用“拉伸(Stretched)”方式展示的,圖片的樣式直接成為灰色調了,而我需要的是“RGB合成(RGB Composite)”。因此我使用了這種“先建立臨時柵格圖層,再新增到mxd”的方式。
釋出地圖文件
我們都知道,在ArcGIS10.1之後都是先將mxd文件轉為sd文件,再進行釋出的。這樣做也是有好處的(聽說是),不再贅述。官網文件給出兩種方式釋出地圖,一是需要ArcGIS Server伺服器名、使用者名稱和密碼方式驗證登入,二是採用arcgis server連線檔案ags的方式直接驗證。我採用了後者,比如我的連線檔案位置在“C:\Users\Administrator\AppData\Roaming\ESRI\Desktop10.2\ArcCatalog\arcgis on WIN-20150327EEH_6080 (admin).ags”。直接引用這個地址作為引數傳入下面這個函式就行。
# 釋出服務
def PublishService(mxdpath,agspath):
new_mxd = arcpy.mapping.MapDocument(mxdpath)
dirname=os.path.dirname(mxdpath)
mxdname=os.path.basename(mxdpath)
dotindex=mxdname.index('.')
servicename=mxdname[0:dotindex]
sddraft = os.path.abspath(servicename + '.sddraft')
sd = os.path.abspath(servicename + '.sd')
if os.path.exists(sd):
os.remove(sd)
#建立服務定義草稿draft
arcpy.CreateImageSDDraft(new_mxd, sddraft, servicename, 'ARCGIS_SERVER', agspath,False,None, "Ortho Images", "ortho images,image service")
#分析草稿draft
analysis = arcpy.mapping.AnalyzeForSD(sddraft)
#列印分析結果
print "The following information was returned during analysis of the MXD:"
for key in ('messages', 'warnings', 'errors'):
print '----' + key.upper() + '---'
vars = analysis[key]
for ((message, code), layerlist) in vars.iteritems():
print '', message, ' (CODE %i)' % code
print ' applies to:',
for layer in layerlist:
print layer.name,
print
# 傳送草稿至伺服器
if analysis['errors'] == {}:
# Execute StageService. This creates the service definition.
arcpy.StageService_server(sddraft, sd)
# Execute UploadServiceDefinition. This uploads the service definition and publishes the service.
arcpy.UploadServiceDefinition_server(sd, agspath)
print "Service successfully published"
else:
print "Service could not be published because errors were found during analysis."
print arcpy.GetMessages()
return agspath.replace(".ags","/"+servicename+".MapServer")
同樣兩個引數,這個函式需要mxd文件路徑和ags檔案路徑作為引數。中間需要先生成服務定義檔案(.sd)和服務定義草稿檔案(.sddraft),然後對sddraft檔案進行分析,若中間沒有出現錯誤,現將其傳送至伺服器。然後再將sd檔案,通過ags檔案傳送至伺服器,更新服務。
這段程式碼最後我返回了剛剛釋出的服務地址,後面將會用到。
製作伺服器快取
注意喲,這裡是定義伺服器快取,不是生成切片地方。這裡是定義,下一步才是生成。就好比“開啟存有《名偵探柯南》的雲盤資料夾”,下一步才是“點選播放按鈕”。
#製作地圖伺服器快取
def CreateCache(inputService,cachepath):
# List of input variables for map service properties
tilingSchemeType = "NEW"
scalesType = "CUSTOM"
numOfScales = "5"#此引數無效
scales = [500000000,250000000,125000000,64000000,32000000]
dotsPerInch = "96"
tileOrigin = "0 0"
tileSize = "256 x 256"
cacheTileFormat = "PNG8"
tileCompressionQuality = ""
storageFormat = "COMPACT"
predefinedTilingScheme = ""
try:
starttime = time.clock()
result = arcpy.CreateMapServerCache_server(inputService,cachepath,tilingSchemeType, scalesType, numOfScales, dotsPerInch,tileSize, predefinedTilingScheme,tileOrigin, scales,cacheTileFormat,tileCompressionQuality,storageFormat)
# print messages to a file
while result.status < 4:
time.sleep(0.2)
resultValue = result.getMessages()
print "completed " + str(resultValue)
except Exception, e:
# If an error occurred, print line number and error message
tb = sys.exc_info()[2]
print "Failed at step 1 \n" "Line %i" % tb.tb_lineno
print e.message
print "Executed creation of Map server Cache schema "
這裡主要用了CreateMapServerCache_server
這個函式,具體函式的意義這裡就不再多說了,完全可以自己去檢視ArcGIS文件。我是使用了自定義的方式切片,可以採用STANDARD方式切片,然後設定numOfScales的值就可以了。
生成切片
這裡才是生成切片的地方,用到的是Arcpy的ManageMapServerCacheTiles_server
函式,同樣具體的引數還是需要自己去查官方文件。幫助文件上面說引數waitForJobCompletion
的值為“WAIT”時,可以從其他地方查到具體的切片進度,這裡我還沒去做,看以後的需求吧。
#生成瓦片
def CreateTiles(inputService):
scales = ""
numOfCachingServiceInstances = 2
updateMode = "RECREATE_ALL_TILES"
areaOfInterest = ""
waitForJobCompletion = "WAIT"
updateExtents = ""
try:
result = arcpy.ManageMapServerCacheTiles_server(inputService, scales,updateMode,numOfCachingServiceInstances,areaOfInterest, updateExtents,waitForJobCompletion)
#print messages to a file
while result.status < 4:
time.sleep(0.2)
resultValue = result.getMessages()
print "completed " + str(resultValue)
print "Created cache tiles for given schema successfully"
except Exception, e:
# If an error occurred, print line number and error message
tb = sys.exc_info()[2]
print "Failed at step 1 \n" "Line %i" % tb.tb_lineno
print e.message
print "Created Map server Cache Tiles "
這個函式需要上一步返回的引數,就是那個以“*.MapServer”結尾的全路徑。
壓縮切片檔案
將切片打包主要是為了便於使用者下載,線上預覽的話就不需要這一步了。下面的程式碼是參考了linda1000的博文,有興趣的可以自己前去看看。
這裡的兩個引數分別是要壓縮的切片路徑,然後是壓縮後的輸出路徑(注意這裡要以*.zip結尾,包括壓縮檔名).
# MakeZipFile
def MakeZipFile(filepath,zippath):
filelist = []
#Check input ...
fulldirname = os.path.abspath(filepath)
fullzipfilename = os.path.abspath(zippath)
print "Start to zip %s to %s ..." % (fulldirname, fullzipfilename)
if not os.path.exists(fulldirname):
print "Dir/File %s is not exist" % fulldirname
return
if os.path.isdir(fullzipfilename):
tmpbasename = os.path.basename(filepath)
fullzipfilename = os.path.normpath(os.path.join(fullzipfilename, tmpbasename))
#Get file(s) to zip ...
if os.path.isfile(filepath):
filelist.append(filepath)
filepath = os.path.dirname(filepath)
else:
#get all file in directory
for root, dirlist, files in os.walk(filepath):
for filename in files:
filelist.append(os.path.join(root,filename))
#Start to zip file ...
destZip = zipfile.ZipFile(fullzipfilename, "w")
for eachfile in filelist:
destfile = eachfile[len(filepath):]
print "Zip file %s..." % destfile
destZip.write(eachfile, destfile)
destZip.close()
print "Zip folder succeed!"
呼叫
這段程式碼我是使用Java runtime直接呼叫的,可以使用這種方式一套流程走下來。但也有不大合適的地方,比如瓦片被刪掉了,我只想生成一下切片等等,這樣就不合適了。
#********************************************
# 主 函 數 *
#********************************************
# 程式執行所需引數:
# 1.圖片所在位置(filepath+filename)
# 2.mxd模板位置(filepath+filename)
# 3.arcgis server連線檔案(ags)位置
# 4.生成切片快取位置(out_path)
# 5.生成壓縮檔案位置(out_path+zipname)
if __name__ == '__main__':
#驗證路徑是否正確
imagepath=sys.argv[1]
mxdpath=sys.argv[2]
agspath=sys.argv[3]
cachepath=sys.argv[4]
zippath=sys.argv[5]
if not os.path.isfile(imagepath):
print "圖片檔案不存在"
sys.exit()
if not os.path.isfile(mxdpath):
print "mxd模板不存在"
sys.exit()
if not os.path.isfile(agspath):
print "arcgis server連線檔案不存在"
sys.exit()
if not os.path.isdir(cachepath):
print "快取目錄不存在"
sys.exit()
# 建立mxd
new_mxd=CreateMxd(imagepath,mxdpath)
print "Finished CreateMxd"
# 釋出服務
inputService=PublishService(new_mxd,agspath)
print "Finished PublishService"
# 製作伺服器快取
CreateCache(inputService,cachepath)
print "Finished CreateCache"
# 生成瓦片
CreateTiles(inputService)
print "Finished CreateTiles"
# 壓縮檔案
tcachepath=os.path.abspath(cachepath+"/"+new_mxd[new_mxd.rindex("\\")+1:new_mxd.rindex(".")])
print tcachepath
MakeZipFile(tcachepath,zippath)
print "Finished MakeZipFile"
更新
使用過程中,發現無法使得切出更小比例尺的切片,最小的切片也是1:500000000。但當用戶上傳的圖片過大時,切出的圖片無法在檢視內完整的看到全貌。這顯然不合理,最後終於找到了解決方法。草稿檔案上傳至伺服器前對其進行修改,sddraft檔案是指上是一個符號xml標準的檔案。使用python直接可以進行操作,將裡面的minScale改為更大
#修改草稿draft
# read sddraft xml
doc = DOM.parse(sddraft)
# turn on caching in the configuration properties
configProps = doc.getElementsByTagName('ConfigurationProperties')[0]
propArray = configProps.firstChild
propSets = propArray.childNodes
for propSet in propSets:
keyValues = propSet.childNodes
for keyValue in keyValues:
if keyValue.tagName == 'Key':
if keyValue.firstChild.data == "minScale":
# turn on caching
keyValue.nextSibling.firstChild.data = "32000000000"
# output to a new sddraft
if os.path.exists(sddraft): os.remove(sddraft)
f = open(sddraft, 'w')
doc.writexml( f )
f.close()