Week 9: ARC Macro Language (AML)

Back To Week 8 and Data Export | On to Week 10 and Import to LMS

Overview:


General Overview of AML

AML is a robust programming language that allows you access to a range of functionality, from automating common tasks in ARC/INFO, to creating complete GUI-based, multithreaded applications.

AML provides the same general functions as other programming languages. AML is interpreted at run-time rather than compiled. This makes AML somewhat slow, but also makes it easy to debug and modify, and does not depend on platform-specific executables.

Almost anything you do in ARC/INFO can be aided by the use of AML, and some tasks can be completely handled in AML. The catch is that AML can be confusing and difficult to learn. But the online documentation is complete and helpful, and you can always borrow AML code from the many AMLs that are included with the standard ARC/INFO installation. Also highly recommended is the self-study guide ARC Macro Language: Developing ARC/INFO menus and macros with AML (ISBN 1-879102-18-8), available from ESRI.

Clearly, it will be impossible to cover AML without the most cursory treatment here, but at least you will see some of the basic functionality of AML.


Variables

Variables are used throughout AML to hold and reference values. There are a few basic ways to set variable values.

Set values directly by using the &setvar directive (&sv, &s are also equivalent to &setvar, but not as easy to read in an AML):

&setvar X1 = 11
&sv Today := [date -dow]
&s item_name_list [listitem tty4 -poly]

The first example sets the variable x1 equal to 11. The second sets the variable today equal to the weekday returned by the [date] function. The third example sets the variable item_name_list equal to the list of item names in TTY4.PAT.

Note that variable names are case insensitive, and that you have the option of assigning value using the "=" or ":=" operators, or no operator at all.

Variables can also be set by using &do loops (which are covered below).

To use the value of a variable, enclose it in percent signs:

pagesize %x1% 8.5

sets the pagesize equal to 11 by 8.5

Local variables are available only within the AML in which they are set; once the AML terminates, the variables are deleted. You can make the variables persist by making them global. To set and use global variables, use the same syntax, but precede the variable name with a dot, e.g., &sv .y1 8.5; pagesize %x1% %.y1%.

You can list the currently set variables with the &lv directive.

You can delete variables with the &dv directive.


Directives

Directives are AML commands. These directives tell the AML processor to perform an action, but there is not necessarily an immediate or specific response to that action. Directives always begin with an ampersand (&). The ampersand distinguishes AML directives from ARC/INFO commands. Directives also work within any module of ARC/INFO other than INFO.

You are already familiar with some directives, such as &wo, which changes the current working directory. &station sets up the ARC/INFO station (terminal, display, digitizer, etc.). &echo toggles the state echoing to the command window. &run runs an AML.

You can find what directives are available by looking at the on-line documentation under Customizing ARC/INFO -> Using AML -> Command References. You can also browse available directives by using the &commands directive, e.g., &commands &c lists all directives starting with "&c."


Functions

Functions perform a task which returns a value to the ARC/INFO session. AML functions are always enclosed in square braces ([]).

You have seen the use of the [delete] function for deleting INFO tables

&sv delstat [delete junk.dat -info]

Functions are especially useful, since they return specific values that can be used to control the ARC/INFO session, to get information about the session, or to set variables.

For example, you can set a variable containing a list of polygon coverages in the current workspace by using the [listfile] directive:

&sv polycovlist [listfile * -cover -poly]

Some especially useful functions are [calc], which returns the result of a calculation, [show], which has a varied syntax depending on the module in which it is used, but always returns a value representing the state of the ARC/INFO system, [extract], which selects a single member of a string. For example, find the x and y distances of the current mapextent in ArcPlot or ArcEdit:

&sv xdist [calc [extract 3 [show mapextent]] - [extract 1 [show mapextent]]]
&sv ydist [calc [extract 4 [show mapextent]] - [extract 2 [show mapextent]]]


Conditional Processing

Frequently in AML, you would like to perform a series of actions if a condition is met, and another series of actions if a different condition is met. You can use the &if directive for testing conditions, and the &do directive to perform the actions. For example, if the map scale is less than 24000, show roads with road names and streams with stream types. Otherwise, do not show road names or stream types:

&if [show mapscale] < 24000 &then
   &do
      arclines roads 2
      annotext roads
      linecolor cyan
      textcolor cyan
      arctext streams type # line # blank
   &end
&else 
   &do
      arclines roads 2
      arclines streams 5
   &end

The way conditional processing works is that every &if directive is evaluated for truth. If the statement is true, then the next command is executed. If the statement is false, then the next command is skipped, and the AML continues. In this case, we group a list of commands together by enclosing them in the &do ... &end directives.

Anticipating the state of the ARC/INFO system, and building this anticipation into your AMLs can save a lot of frustration later. For example, you may want to create an aspect grid from an elevation grid in an AML. If you simply place code in the AML to create the new grid:

asp = aspect (dem)

what happens if the grid ASP already exists? The Grid processor passes an error off to the AML processor, which then terminates the AML. You could optionally do this:

&if [exists asp -grid] &then
   kill asp all
asp = aspect (dem)

or

&if not [exists asp -grid] &then
   asp = aspect (dem)

In the first example, you are re-creating the ASP grid. In the second example, if the ASP grid does not exist, then create it (otherwise do nothing).

Conditionals can add a high degree of flexibility to your programs. Conditionals can also be managed with the &select directive:

&sv mscale [show mapscale]
&select %mscale%
   &when < 24000
      &do
         ... /* a series of actions
      &end
   &when >= 24000 and < 48000
      &do
         ... /* a different series of actions
      &end
   &when >= 48000
      &do
         ... /* a different series of actions
      &end
&end


Looping

Loops are used when you will be performing a series of tasks over and over. Loops can also be used to perform a series of tasks while a current condition is being met or until a future condition is met.

Let's list the projection system for each coverage and grid in the current workspace:

&do data_set &list [listfile * -cover], [listfile * -grid]
   &describe %data_set%
   &type 
   &type %data_set%:
   &type %prj$name%
&end

Here, the function [listfile] generates a list of coverages and a list of grids. The list is used as input to the &do ... &list loop directive, which executes the loop once for each member of the list. The loop control variable (data_set) is initialized by the directive itself. The value of the variable changes for each iteration of the loop, sequentially taking on the values of the members of the list. The &describe directive describes the data set, and then writes a series of variables containing descriptions of the data set (see the online documentation on this directive to find out what it can do). The &type directive simply types text to the command window.

Let's increment a variable's value by 100 while it is less than 1000

&sv x-coord [extract 1 [show select point 1 xy]]
&do &while x-coord < 1000
   &sv x-coord = %x-coord% + 100
&end
...

Here, the initial value of x-coord is the x-coordinate of the first point in the coverage. Each iteration of the loop increases the value of x-coord by 100 as long as the value of x-coord is less than 1000. Once the value increases beyond 1000, the loop terminates and the AML continues.

Let's create a list of the first 6 coverages (alphabetically) in the workspace

&sv count 1
&sv covlist
&do &until %count% = 6
   &sv covlist %covlist%,[extract %count% [listfile * -cover]]
   &sv count = %count% + 1
&end
...

Here, the initial count variable is set to 1. The covlist variable is null to begin with. As the loop iterates, the nth element in the complete list of coverages is appedned to the covlist variable, and the count variable is incremented by 1. Once the count variable reaches the value of 6, the loop terminates and the AML continues.

Loops are often used in combination with conditionals. For example, you could create a loop that runs once for each element in a list of coverages. The current coverage could be described, and if it contains incomplete polygon topology, a BUILD could be performed.


Reading and Writing (ASCII) Files

AML provides the ability to read and write ASCII files. Both reading and writing file happens on a line-sequential basis, which means that you cannot back up or jump around in the file. But even given this constraint, reading and writing ASCII files is an important functionality.

There are a few basic steps to reading files:

  1. Open the file.
  2. Read 0 or more records (lines) from the file.
  3. Act on the value of the record
  4. Repeat reading and acting until the last line of the file is reached
  5. Close the file

Here is a typical scenario where you will need to read a system ASCII file. Suppose you have 500 unique values for stand name in a forest stand coverage. You are interested in making a stand map for each one of these stands on an 11 x 8.5 in sheet. You could use the [listunique] function and perform a &do loop, but the problem with the functions [listunique], [listitem], [listfile], etc., is that there are limits to the length of the string which is returned from the function. When too many elements or characters are in the returned string, you will have to use the {output_file} option, which writes the members of the returned list to an ASCII file, where each member of the list is its own record.

The code follows:

/* run this in ArcPlot
pagesize 11 8.5
shadetype color
shadecolor rgb 200 200 200
shadeput 901
&if [exists stand_nm.txt -file] &then
   &sv delstat [delete stand_nm.txt -file]
&sv numpolys [listunique stands -poly stand_name stand_nm.txt]
&sv stand_file [open stand_nm.txt openstat -read]
&if %openstat% <> 0 &then
   &return File stand_nm.txt could not be opened.
&sv rec [read %stand_file% readstat]
&do &until %readstat% = 102 /* EndOfFile
   clearselect
   reselect stands poly stand_name = %rec%
   display 1040
   m[subst [unquote %rec%] ' ' _].gra   
   mapextent poly stands
   linepen .01
   linecolor black
   box .1 .1 [calc [extract 1 [show pagesize] - .1]] ~
      [calc [extract 2 [show pagesize] - .1]]
   maplimits .1 .1 [calc [extract 1 [show pagesize] - .1]] ~
      [calc [extract 2 [show pagesize] - .1]]
   mapposition cen cen
   mapscale auto
   polygonshades stands 901
   arcs stands
   labeltext stands stand_name # cc
   &sv rec [read %stand_file% readstat]
&end
&sv closeall [close -all]
display 9999

The AML sets up some basic AP settings including pagesize and a shade symbol. Then the file is opened. If the file could not be opened, the program terminates with a message. Then the first record is read. The status of the [read] funtion ("readstat" here) should be 0 if the record was read successfully. The loop runs several commands that create a map, and then reads the next record from the file. When no more records can be read (%readstat% = 102), the loop terminates, and the AML continues.


The basic steps in writing a file:

  1. Open the file.
  2. Write 0 or more records (lines) to the file.
  3. Close the file

The following example selects a line from a coverage and then writes out a file consisting of the x, y, and z values for each vertex on the line.

/* run this in ArcPlot
&sv closeall [close -all]
&if [exists line.txt -file] &then
   &sv delstat [delete line.txt -file]
&sv line_file [open line.txt openstat -write]
&if %openstat% ne 0 &then
   &return The file line.txt could not be opened for writing.
mapextent streams
&label select_again
clearselect
arcs streams
reselect streams arc one *  /* select a stream from the display
&if [extract 1 [show select streams arc]] ne 1 &then
   &goto select_again
&sv line_number [show select streams arc 1 item streams#]
&sv rec [write %line_file% [quote streams - line # = %line_number%]]
&do vertex = 1 &to [show select streams arc 1 xy]
   &sv rec [write %line_file% [quote [show select streams arc 1 xy %vertex%]]]
&end
&sv closeall [close -all]


Although reading and writing files takes a bit of getting used to, it is extremely useful when you need to move data between ARC/INFO and other applications. Often, the standard export formats do not offer what you need. When this is the case, you may be able to get around the problem by creating your own input and output formats with [read] and [write].


Comments

Every AML should be commented. This is very important if a group of analysts needs to work on a common application. It is also important even if you will be the only user. Comments make the code readable, and will allow you to make changes in the future when you have forgotten all of the individual steps you knew at the time when you wrote the program. To make comments in an AML, use the "/*" characters. The AML processor will ignore any text after these characters on the current line. For example, I should have made comments in the above AMLs. Here is the 2nd AML with comments:

/* run this in ArcPlot
/* close any open files
&sv closeall [close -all]
/* delete the output if it exists before creating it
&if [exists line.txt -file] &then
   &sv delstat [delete line.txt -file]
/* open (create) the output file
&sv line_file [open line.txt openstat -write]
/* did it open correctly?
&if %openstat% ne 0 &then
   &return The file line.txt could not be opened for writing.
/* set the map extent to the streams coverage
mapextent streams
/* if no stream is selected, jump here
&label select_again
clearselect
/* draw the streams
arcs streams
/* the user should select one stream
reselect streams arc one *  /* select a stream from the display
/* if <> 1 stream is selected, try again by jumping to the label
&if [extract 1 [show select streams arc]] ne 1 &then
   &goto select_again
/* write the internal-id of the selected stream to a variable
&sv line_number [show select streams arc 1 item streams#]
/* write a header line
&sv rec [write %line_file% [quote streams - line # = %line_number%]]
/* loop through each vertex, writing to the output file
&do vertex = 1 &to [show select streams arc 1 xy]
   &sv rec [write %line_file% [quote [show select streams arc 1 xy %vertex%]]]
&end
/* close any open files
&sv closeall [close -all]


Running AMLs

To run an AML, use the &run directive. If the AML calls for any arguments, enter these also at the command line. You can run AMLs that are not in the current directory by referencing the pathname to the AML:

&run ~phurvitz/atool/arc/csv.aml streams.aat streams.txt

Here, you are running an AML that is not in the current workspace, and you are also providing the necessary arguments to the AML.


Atools: Custom Commands

Some of the commands you have been exposed to are actually not commands in the strictest sense of the word. True ARC/INFO commands (such as BUILD, LATTICEDEM, etc.) are compiled C programs. Some "commands" such as FILL are actually AMLs.

To find out which commands are AMLs, use the COMMANDS command:

Arc: commands reg*
REGIONBUFFER        REGIONCLASS         REGIONCLEAN         REGIONDISSOLVE
REGIONERRORS        REGIONJOIN          REGIONQUERY         REGISTER

ATOOL directory c:\bin\esri\arcexe71\atool\arc
regionpoly          regionpolycount     regionpolylist      regionxarea
regionxtab

Notice that there are a group of commands (in capital letters) and a group of ATOOLs (in lowercase). These atools are actually AMLs in ARC's default atool directory. The only reason these are accessible as commands are because of their location in the file system.

You can make your own AMLs act as commands by creating and referencing an ATOOL directory. You will need to create a directory called atool, which has beneath it subdirectories with names corresponding to ARC/INFO modules (e.g., arc, arcplot, grid, tables). You place AMLs which have been designed for each module in its corresponding subdirectory. For exampel, to use my csv.aml as a command, I have it placed in my $home/atool/arc directory.

The second step in making atools is telling ARC/INFO what atool directories to use. You can do this by using the &atool directive, e.g., &atool $home/atool.

To automate this process, create or edit a file in your home directory called .arc. You can place various AML commands in this file (which is actually another AML). When ARC/INFO starts, it will run this AML, and any commands placed there will be executed. So you can place the command &atool $home/atool in your $home/.arc file, and your atool directory will be automatically specified as soon as you start ARC/INFO.


AML and Menu File Locations

In addition to the ATOOL directory, you can specify AML and MENU directories. For these directories, once specified, the ARC processor will automatically look for AMLs and menus which you attempt to run. In other words, if you have specified an AML directory, you do not need to enter the full path of the AML if you want to run it; simply enter the AML name. For example,

Arc: &sys cat ../joke.aml
&type This really is a joke.
Arc: &r joke
AML ERROR - Unable to run file joke
Arc: &show &amlpath
' '
Arc: &amlpath ..
Arc: &r joke
This really is a joke.

Here I have placed the AML in the parent directory and listed the contents of the file (cat...). When I try to run the AML, it does not run, since it is not in the current directory. However, I add the parent directory (..) to the amlpath, and the AML runs without incident.

You can also set a path for menus by using the &menupath directive. Although we have not covered menus in this discussion, you may want to research this on your own.


Exercises

Write a series of AMLs to automate some of the tasks you have been assigned in previous lessons. Some suggestions:

Create a single AML to make all the MOSS files and USGS-format DEM from last week's lesson.


Back To Week 8 and Data Export | On to Week 10 and Import to LMS