スクリプト ツールでのメッセージの書き込み
スクリプトで ArcPy をインポートするには、次のように指定する必要があります。
import arcpy
スクリプトをスクリプト ツールとして実行すると、ArcMap や ArcCatalog などの呼び出し元アプリケーションが、ArcPy に完全に認識されます。実際に使用されるオブジェクトは、アプリケーションが使用しているのと同じオブジェクトです(新規のオブジェクトは作成されません)。たとえば、overwriteOutput や scratchWorkspace など、アプリケーションで行ったすべての環境設定が使用できます。ArcPy でメッセージを作成し、その自作メッセージを進捗状況ダイアログ ボックス、ツールの結果ウィンドウ、および Python ウィンドウに自動的に表示できることは、スクリプト ツールによる大きな効果の 1 つです。また、スクリプト ツールでは、スクリプト ツールの呼び出し元であるモデルまたはスクリプト ツールのいずれからも自作メッセージにアクセスできます。
スクリプト ツールとは対照的に、スタンドアロン スクリプトでは ArcGIS アプリケーションからスクリプトが呼び出されるので、メッセージの閲覧が可能な進捗状況ダイアログ ボックス、結果ウィンドウ、または Python ウィンドウはありません。スクリプト内から別の(スタンドアロン)スクリプトを呼び出した場合、呼び出されたスクリプト(ジオプロセシング ツールを使用するスクリプト)が自作メッセージとはまったく違う別個のものであるため、スクリプト間でのメッセージ共有は不可能です。スクリプト ツールを利用すれば、その主要メリットの 1 つであるメッセージ共有および環境共有を実現できます。
メッセージ作成用の ArcPy 関数は、次の 4 通りです。
- AddMessage("メッセージ") - 一般情報メッセージ用(重要度 = 0)。
- AddMessage("メッセージ") - 警告用(重要度 = 1)。
- AddMessage("メッセージ") - エラー用(重要度 = 2)。
- AddIDMessage(MessageType, MessageID, AddArgument1, AddArgument2) - エラーと警告用(MessageType 引数が重要度を決定します)。AddIDMessage() を呼び出すと、短いメッセージとメッセージ ID が表示されます。メッセージ ID は、問題の原因と解決策の説明へのリンクです。エラー メッセージを追加する場合(AddError() または AddIDMessage() を使用)、処理は次のようになります。
- スクリプトは、実行し続けます。適切なエラー処理ロジックを追加したり、スクリプトの実行を停止したりするのは、作成者です。たとえば、中間ファイルやカーソルを削除する必要がある場合があります。
- スクリプトが終了したら、呼び出したスクリプトまたはモデルはシステム エラーを受信し、実行が停止されます。
メッセージの追加の例
次の例では、あるワークスペースから別のワークスペースへ、フィーチャクラスのリストをコピーします。ジオデータベースからフォルダのように、2 つのワークスペースが異なるタイプの場合、自動的に変換されます。エラー処理は、問題を捕捉し、メッセージを返すために使用されます。問題がなければ、実行中に正常処理の情報メッセージが返されます。この例では、from <module> import ディレクティブを利用しています。この場合、モジュールは ScriptUtils で、そのコードも後述しています。このコードは、try/except ブロックも使用しています。
# ConvertFeatures.py # Converts feature classes by copying them from one workspace to another # # Import the ScriptUtils utility module which, in turn, imports the # standard library modules and imports arcpy # from ScriptUtils import * # Get the list of feature classes to be copied to the output workspace # in_feature_classes = arcpy.GetParameterAsText(0) # Establish an array of input feature classes using the ScriptUtils routine. # in_feature_classes = SplitMulti(in_feature_classes) # Get the output workspace # out_folder = arcpy.GetParameterAsText(1) # Loop through the array copying each feature class to the output workspace # for in_feature_class in in_feature_classes: try: # Create the output name # feature_class_name = arcpy.ValidateTableName(in_feature_class, out_folder) # Add an output message # arcpy.AddMessage("Converting: " + in_feature_class + " To " +\ feature_class_name + ".shp") # Copy the feature class to the output workspace using the ScriptUtils routine # CopyFeatures(in_feature_class, out_folder + os.sep + \ feature_class_name) # If successful, add another message # arcpy.AddMessage("Successfully converted: " + in_feature_class + \ " To " + out_folder) except StandardError, ErrDesc: arcpy.AddWarning("Failed to convert: " + in_feature_class) arcpy.AddWarning(ErrDesc) except: arcpy.AddWarning("Failed to convert: " + in_feature_class) if not arcpy.GetMessages(2) == "": arcpy.AddError(arcpy.GetMessages(2))
以下は、上で使用している ScriptUtils モジュールです。
# ScriptUtils.py # Import required modules # import arcpy import sys import string import os def SplitMulti(multi_input): try: # Split the multivalue on the semi-colon delimiter # multi_as_list = string.split(multi_input, ";") return multi_as_list except: ErrDesc = "Error: Failed in parsing the inputs." raise StandardError, ErrDesc def CopyFeatures(in_table, out_table): try: ErrDesc = "CopyFeatures failed" # Copy each feature class to the output workspace # arcpy.CopyFeatures_management(in_table, out_table) except: if arcpy.GetMessages(2) != "": ErrDesc = arcpy.GetMessages(2) raise StandardError, ErrDesc
上のスクリプトに関する注意
スクリプトを簡素化し、コードを一元化するために、他の Python スクリプトから関数やモジュール、オブジェクトをインポートできます。import ステートメントを使用すると、別のスクリプトからすべて、あるいは必要なエンティティだけを利用できます。
例外は、プログラムの制御フローを変更するイベントです。例外は、try および raise ステートメントを使用して、スクリプト内で発動またはインターセプトされます。StandardError は内蔵されている例外で、Python のほとんどのタイプの例外が基盤としています。これは、ツールでエラーが発生し、ツールのエラー メッセージによって設定された ErrDesc 変数が設定された場合に呼び出されます。
string モジュール内で定義された文字列は、テキストを格納および表現するために使用される内蔵のタイプです。文字列の操作には、連結やスライス、インデックスなど、多くの処理がサポートされています。
オペレーティング システム(os)モジュールは、オペレーティング システムの基本ツール セットへの汎用インタフェースを提供します。
ツールからのすべてのメッセージのリターン
呼び出したツールから、メッセージの重要度に関係なくすべてのメッセージを返したい場合があります。AddReturnMessage 関数は、インデックス パラメータを使用して、ArcPy のメッセージ配列からメッセージを返します。次の例では、ツールのすべてのメッセージを返しています。
arcpy.Clip_analysis("roads","urban_area","urban_roads") # Return the resulting messages as script tool output messages # x = 0 while x < arcpy.MessageCount: arcpy.AddReturnMessage(x) x = x + 1
2 つの目的を兼ねるスクリプトのメッセージング
スクリプトは、スタンドアロン スクリプト(オペレーティング システムから利用)またはスクリプト ツールという 2 つの目的で使用できるように設計できます。これには、考慮すべきことが 3 つあります。
- スクリプトをスタンドアロンとして実行する場合、メッセージを表示できません(ArcMap など、メッセージを表示できるアプリケーションがありません)。代わりに、print ステートメントを使用すると、メッセージをコマンド ウィンドウ(標準出力)に表示できます。
- スクリプトをツールとして実行すると、メッセージは進捗状況ダイアログ ボックス、結果ウィンドウ、および Python ウィンドウ(ツールを Python ウィンドウで実行している場合)に表示されます。print ステートメントを使用する必要はありません。それでも print ステートメントを使用する場合は、ツールのプロパティの [ソース] タブにある [スクリプト実行時にコマンド ウィンドウを表示] チェックボックスをオンにする必要があります。スクリプト ツールの実行中にコマンド ウィンドウを表示させることは、見栄えも悪く、想定外のときに表示されなくなることも多いのでお勧めしません。
- スタンドアロン スクリプトとして実行する場合、呼び出したプログラムが捕捉できるように、例外を発生させます。スクリプト ツールとして実行しているときに例外を発生させるには、AddError() 関数を使用します。
一般的には、メッセージを標準出力(print を使用)と ArcPy(AddMessage、AddWarning、AddError の各関数を使用)の両方で書き込むエラー報告ルーチンを記述します。以下は、[解析] ツールボックスの [多重リング バッファ(Multiple Ring Buffer)] ツールの中から、このようなルーチンを抜粋したものです。[多重リング バッファ(Multiple Ring Buffer)] スクリプトを表示するには、[解析] ツールボックスを右クリックして、[編集] をクリックします。
def AddMsgAndPrint(msg, severity=0): # Adds a Message (in case this is run as a tool) # and also prints the message to the screen (standard output) # print msg # Split the message on \n first, so that if it's multiple lines, # a GPMessage will be added for each line try: for string in msg.split('\n'): # Add appropriate geoprocessing message # if severity == 0: arcpy.AddMessage(string) elif severity == 1: arcpy.AddWarning(string) elif severity == 2: arcpy.AddError(string) except: pass
進捗状況ダイアログ ボックスの制御
スクリプト ツールはアプリケーションを共有するので、進捗状況ダイアログ ボックスを制御できます。次の図のように、デフォルト プログレッサとステップ プログレッサのいずれかを選択することで、進捗状況ダイアログ ボックスの外観を制御できます。
進捗状況ダイアログ ボックスとそのプログレッサを制御するには、4 つの関数を使用します。
機能 |
説明 |
---|---|
プログレッサのタイプ(デフォルトまたはステップ)、ラベル、最小値と最大値、ステップ プログレッサの場合は間隔を設定します。 |
|
プログレッサをリセットします |
|
ステップ プログレッサを増分だけ移動します |
|
プログレッサのラベルを変更します |
次のコードは、デフォルト プログレッサとステップ プログレッサの使用法を示したものです。このコードを Python エディタにコピーし、保存してから、そのスクリプト ツールを作成してください。コードのコメントに説明されているように、このスクリプト ツールには、Long の 2 つの入力パラメータがあります。次に、スクリプト ツールを実行し、パラメータに異なる値を設定します(n = 10 および p = 1 で開始し、次に n = 101 および p = 3 で実行します)。
# Demonstration script showing examples of using the progressor # Parameters: # n - number to count to (a good first choice is 10) # p - interval to count by (a good first choice is 1) # The various time.sleep() calls are just to slow the dialog down # so you can view messages and progressor labels. # import arcpy import time n = int(arcpy.GetParameterAsText(0)) p = int(arcpy.GetParameterAsText(1)) readTime = 2.5 # Pause to read what's written on dialog loopTime = 0.3 # Loop iteration delay arcpy.AddMessage("Running demo with: " + str(n) + " by " + str(p)) # Start by showing the default progress dialog, where the # progress bar goes back and forth. Note how the progress label # mimics working through some "phases", or chunks of work that # a script may perform. # arcpy.SetProgressor("default", "This is the default progressor") time.sleep(readTime) for i in range(3): arcpy.SetProgressorLabel("Working on \"phase\" " + str(i + 1)) arcpy.AddMessage("Messages for phase " + str(i+1)) time.sleep(readTime) arcpy.AddMessage("-------------------------") # Setup the progressor with its initial label, min, max, and interval # arcpy.SetProgressor("step", "Step progressor: Counting from 0 to " + str(n), 0, n, p) time.sleep(readTime) # Loop issuing a new label when the increment is divisible by the # value of countBy (p). The "%" is python's modulus operator - we # only update the position every p'th iteration # for i in range(n): if (i % p) == 0: arcpy.SetProgressorLabel("Iteration: " + str(i)) arcpy.SetProgressorPosition(i) time.sleep(loopTime) # Update the remainder that may be left over due to modulus operation # arcpy.SetProgressorLabel("Iteration: " + str(i+1)) arcpy.SetProgressorPosition(i+1) arcpy.AddMessage("Done counting up") arcpy.AddMessage("-------------------------") time.sleep(readTime) # Just for fun, make the progressor go backwards. # arcpy.SetProgressor("default", "Default progressor: Now we'll do a countdown") time.sleep(readTime) arcpy.AddMessage("Here comes the countdown...") arcpy.SetProgressor("step", "Step progressor: Counting backwards from " + str(n), 0, n, p) time.sleep(readTime) arcpy.AddMessage("Counting down now...") for i in range(n, 0, -1): if (i % p) == 0: arcpy.SetProgressorLabel("Iteration: " + str(i)) arcpy.SetProgressorPosition(i) time.sleep(loopTime) # Update for remainder # arcpy.SetProgressorLabel("Iteration: " + str(i-1)) arcpy.SetProgressorPosition(i-1) time.sleep(readTime) arcpy.AddMessage("-------------------------") arcpy.AddMessage("All done") arcpy.ResetProgressor()
最大値が大きい場合の適切なインクリメントの選択
反復処理の回数がわからないスクリプトを記述することは珍しくありません。たとえば、テーブル内の行数が事前にわからないまま、スクリプトが SearchCursor を使用してすべての行に反復処理を行う場合があります。スクリプトは、数千行から数百万行まで、任意のサイズのテーブルで使用される可能性があります。大きなテーブルの行ごとにステップ プログレッサを増やしていくことは、パフォーマンス上のボトルネックになるため、このようなパフォーマンス上のボトルネックの対策を取る必要があります。
ステップ プログレッサのパフォーマンス上の問題を明らかにして評価するには、次のコードを Python エディタにコピーし、保存してから、そのスクリプト ツールを作成します。このツールには、テーブル パラメータとフィールド パラメータの 2 つの入力があります。スクリプト ツールをさまざまなサイズのテーブルを使用して実行します。必ず 10,000 以上の行を含むテーブルまたはフィーチャクラスを試して、パフォーマンスの違いを調べます(ツールをプロセス内とプロセス外で実行してみて、プロセス内で実行した場合のパフォーマンスの向上を確認することもできます)。
このスクリプトは、3 つのループを実行します。各ループでは、テーブルのすべての行を取得しています。これらのループは、ステップ プログレッサの更新方法が異なります。最初と 2 番目のループは、ステップ プログレッサを大きな増分で更新しています。最後のループは、各行ごとにステップ プログレッサを増加させています。ツールを実行すると、この最後のループの実行に時間がかかることがわかります。
このコードにある手法をスクリプト ツールに利用してください。
# Demonstrates a step progressor by looping through records # on a table. Use a table with 10,000 or so rows - smaller tables # just whiz by. # 1 = table name # 2 = field on the table import arcpy try: inTable = arcpy.GetParameterAsText(0) inField = arcpy.GetParameterAsText(1) # Determine n, number of records on the table # arcpy.AddMessage("Getting row count") n = arcpy.GetCount_management(inTable) if n == 0: raise "no records" arcpy.AddMessage("Number of rows = " + str(n)) arcpy.AddMessage("") arcpy.AddMessage("---------------------------------") # Method 1: Calculate and use a suitable base 10 increment # =================================== import math p = int(math.log10(n)) if not p: p = 1 increment = int(math.pow(10, p-1)) arcpy.SetProgressor("step", "Incrementing by " + str(increment) + " on " + \ inTable, 0, n, increment) rows = arcpy.SearchCursor(inTable) i = 0 beginTime = time.clock() for row in rows: if (i % increment) == 0: arcpy.SetProgressorPosition(i) fieldValue = row.getValue(inField) i = i + 1 arcpy.SetProgressorPosition(i) arcpy.AddMessage("Method 1") arcpy.AddMessage("Increment = " + str(increment)) arcpy.AddMessage("Elapsed time: " + str(time.clock() - beginTime)) arcpy.AddMessage("---------------------------------") del rows del row # Method 2: let's just move in 10 percent increments # =================================== increment = int(n/10.0) arcpy.SetProgressor("step", "Incrementing by " + str(increment) + " on " + inTable, \ 0, n, increment) rows = arcpy.SearchCursor(inTable) i = 0 beginTime = time.clock() for row in rows: if (i % increment) == 0: arcpy.SetProgressorPosition(i) fieldValue = row.getValue(inField) i = i + 1 arcpy.SetProgressorPosition(i) arcpy.AddMessage("Method 2") arcpy.AddMessage("Increment = " + str(increment)) arcpy.AddMessage("Elapsed time: " + str(time.clock() - beginTime)) arcpy.AddMessage("---------------------------------") del rows del row # Method 3: use increment of 1 # =================================== increment = 1 arcpy.SetProgressor("step", "Incrementing by 1 on " + inTable, 0, n, increment) rows = arcpy.SearchCursor(inTable) beginTime = time.clock() while row: arcpy.SetProgressorPosition() fieldValue = row.getValue(inField) arcpy.SetProgressorPosition(n) arcpy.ResetProgressor() arcpy.AddMessage("Method 3") arcpy.AddMessage("Increment = " + str(increment)) arcpy.AddMessage("Elasped time: " + str(time.clock() - beginTime)) arcpy.AddMessage("---------------------------------") arcpy.AddMessage("") arcpy.AddMessage("Pausing for a moment to allow viewing...") time.sleep(2.0) # Allow viewing of the finished progressor del rows del row except "no records": arcpy.AddWarning(inTable + " has no records to count") except: if rows: del rows if row: del row arcpy.AddError("Exception occurred")