Have you ever needed to do just a little bit or perhaps much
more to label a featureclass in ArcMap than the standard
ESRI or even Maplex labeling engine would allow? Me too.
Lucky for us, with the release of ArcGIS 8.1, ESRI
introduced "advanced" labeling to all licensing levels using
the FindLabel function and your choice of two scripting
languages: VBScript and JScript. Advanced labeling along
with the FindLabel function provides a way to
programmatically define the text that displays when labeling
features. Through the use of code examples, we will focus
on using VBScript and FindLabel functions to do advanced
labeling of features by incorporating scripting functionality
such as conditional logic, arrays, and regular expressions.
With a little (or sometimes a lot) of code, users can either
simplify or add complexity to their feature labels without
having to modify the underlying data, create temporary
datasets for the sole purpose of labeling, or convert labels
to annotation and - oh, the horror - manually arrange labels
by hand.
Inspirations/other resources
Hankley, Chip. Using VBScript to Build Complex Labels in
ArcGIS. ArcUser. October-December 2004: 50-53. Online
at http://www.esri.com/news/arcuser/1104/files/
vbscript_label.pdf
Hoque, Mohammed A. Labeling Features Using Attributes
from an External Database. ArcUser July-September 2005:
52-53. Online at http://www.esri.com/news/arcuser/0705/
files/externaldb.pdf
exists
This poster available as a pdf at: http://www.super-cooper.com/okscaug
1 Function FindLabel ([TOTNETAC], [NETNP], [NETPR], [NETHBP], [NETHBO], _
2 [TOTNETFED], [NETFEDNP], [NETFEDPR], [NETFEDHBP], [NETFEDHBO])
3
4 Dim iMaxValSz, h, i, j, k, space, intIndex, fedIntIndex, netArray, strLabel, strCarReturns, _
5 strFedLabel, mathNP, mathHBP, mathPR
6 iMaxValSz = 0 ' set max value size initial value to zero
7 ' Set our field values into variables
8 totnetac = [TOTNETAC]
9 netnp = [NETNP]
10 netpr = [NETPR]
11 nethbp = [NETHBP]
12 nethbo = [NETHBO]
13 fednp = [NETFEDNP]
14 fedpr = [NETFEDPR]
15 fedhbp = [NETFEDHBP]
16 ' SECTIONS WITH FEDERAL LEASES
17 If FormatNumber([TOTNETFED]) > 0 Then
18 ' Do some math, compare fed to non‐fed NP/HBP/PR, if arent equal, get the difference
19 ' between the two, the non‐federal portion. If equal, set var to zero. Var goes into
20 ' array, if zero, ignore it, if non‐zero, display as non‐federal acreage
21 ' Compare NP/FNP
22 If [NETNP] = [NETFEDNP] Then
23 mathNP = "0"
24 Else
25 If ([NETNP] ‐ [NETFEDNP]) <= 0.5 Then
26 mathNP = FormatNumber(([NETNP] ‐ [NETFEDNP]),2)
27 ElseIf Right((FormatNumber(([NETNP] ‐ [NETFEDNP]),2)),2) = "00" Then
28 mathNP = FormatNumber(([NETNP] ‐ [NETFEDNP]),0)
29 ElseIf Right((FormatNumber(([NETNP] ‐ [NETFEDNP]),2)),2) <> "00" Then
30 mathNP = FormatNumber(([NETNP] ‐ [NETFEDNP]),2)
31 End If
32 End If
33 ' Compare HBP/FHBP
34 If [NETHBP] = [NETFEDHBP] Then
35 mathHBP = "0"
36 Else
37 If ([NETHBP] ‐ [NETFEDHBP]) <= 0.5 Then
38 mathHBP = FormatNumber(([NETHBP] ‐ [NETFEDHBP]),2)
39 ElseIf Right((FormatNumber(([NETHBP] ‐ [NETFEDHBP]),2)),2) = "00" Then
40 mathHBP = FormatNumber(([NETHBP] ‐ [NETFEDHBP]),0)
41 ElseIf Right((FormatNumber(([NETHBP] ‐ [NETFEDHBP]),2)),2) <> "00" Then
42 mathHBP = FormatNumber(([NETHBP] ‐ [NETFEDHBP]),2)
43 End If
44 End If
45 ' Compare PR/FPR
46 If [NETPR] = [NETFEDPR] Then
47 mathPR = "0"
48 Else
49 If ([NETPR] ‐ [NETFEDPR]) <= 0.5 Then
50 mathPR = FormatNumber(([NETPR] ‐ [NETFEDPR]),2)
51 ElseIf Right((FormatNumber(([NETPR] ‐ [NETFEDPR]),2)),2) = "00" Then
52 mathPR = FormatNumber(([NETPR] ‐ [NETFEDPR]),0)
53 ElseIf Right((FormatNumber(([NETPR] ‐ [NETFEDPR]),2)),2) <> "00" Then
54 mathPR = FormatNumber(([NETPR] ‐ [NETFEDPR]),2)
55 End If
56 End If
57 ' Build array for FED values, if mathNP/mathHBP/mathPR are zero, theyre ignored below when
58 ' tested, otherwise, they are included in the array explosion as non‐fed acreage
59 fedArray = Array(Array(totnetac, "", " CNT"), _
60 Array(mathNP, "", " NP"), _
61 Array(mathHBP, "", " HBP"), _
62 Array(mathPR, "", " PR"), _
63 Array(fednp, "", " FNP"), _
64 Array(fedpr, "", " FPR"), _
65 Array(fedhbp, "", " FHBP"))
66 ' Determine length of longest acreage string in array
67 ' Use it later to center longer strings in section
68 For i = 0 To UBound(fedArray)
69 j = fedArray(i)
70 If (Len(j(1)) > iMaxValSz) Then
71 iMaxValSz = Len(j(1))
72 End If
73 Next
74 ' START MAKING LABEL
75 strFedLabel = ""
76 ' Explode array values, if they are > 0
77 k = fedArray(0)
78 fedIntIndex = 0
79 For h = 0 To UBound(fedArray)
80 k = fedArray(h)
81
82 If iMaxValSz > 2 Then ' push longer acreage strings over to
83 space = " " ' the left a little, center in section
84 Else
85 space = ""
86 End If
87
88 If k(0) > 0 Then
89 strFedLabel = strFedLabel & space & k(1) & k(0) & k(2) & vbNewLine
90 fedIntIndex = fedIntIndex + 1 ' count lines for non‐zero hits from array
91 End If
92 Next
93 ' Determine how many carriage returns are needed to top align
94 ' acreage list in the section polygon, based on line hits above
95 Select Case fedIntIndex
96 Case 2
97 strCarReturns = vbNewLine & vbNewLine
98 Case 3
99 strCarReturns = vbNewLine
100 Case Else
101 strCarReturns = ""
102 End Select
103 ' FINAL BUILD OF LABEL
104 FindLabel = strFedLabel & "" & strCarReturns
105
106 Else
107 ' SECTIONS WITHOUT FEDERAL LEASES
108 ' Build nested array of field values for non‐federal acreage
109 netArray = Array(Array(" CNT", totnetac, ""), _
110 Array(" NP", netnp, ""), _
111 Array(" PR", netpr, ""), _
112 Array(" HBP", nethbp, ""), _
113 Array(" HBO", hbo, ""))
114 ' Determine length of longest acreage string in array
115 ' Use it later to center longer strings in section
116 For i = 0 To UBound(netArray)
117 j = netArray(i)
118 If (Len(j(1)) > iMaxValSz) Then
119 iMaxValSz = Len(j(1))
120 End If
121 Next
122 ' START BUILDING THE LABEL
123 strLabel = ""
124 ' Loop thru array, get our values, if not = 0
125 j = netArray(0) ' reset j to be first sub‐array in netArray
126 intIndex = 0 ' reset array counter
127 For i = 0 To UBound(netArray)
128 j = netArray(i)
129
130 If iMaxValSz > 2 Then ' push longer acreage strings over to
131 space = " " ' the left a little, center in section
132 Else
133 space = ""
134 End If
135
136 If j(1) > 0 Then ' test for zero values, skip em
137 strLabel = strLabel & space & j(2) & j(1) & j(0) & vbNewLine
138 intIndex = intIndex + 1 ' count how many returns we get
139 End If ' from our array, only non‐zero hits
140 Next
141 ' Determine how many carriage returns are needed to top align
142 ' acreage list in the section polygon, based on line hits above
143 Select Case intIndex
144 Case 2
145 strCarReturns = vbNewLine & vbNewLine
146 Case 3
147 strCarReturns = vbNewLine
148 Case Else
149 strCarReturns = ""
150 End Select
151 ' FINAL BUILD OF NON‐FEDERAL LABEL
152 FindLabel = strLabel & strFedLabel & "" & strCarReturns
153 End If
154 End Function
The Problem
You have a dataset with multiple attributes
you want to label with, but when values are
absent (zero or null), you want to skip that
attribute. Also, it’s a polygon featureclass,
so absolutely positioning the labels can be
difficult.
1 Function FindLabel ([TOTNETAC],[NETPR],[NETNP],[NETHBP],
[NETHBO],[NETFEDPR],[NETFEDNP],[NETFEDHBP])
2 FindLabel = [TOTNETAC] & vbNewLine & [NETPR] & vbNewLine & _
3 [NETNP] & vbNewLine & [NETHBP] & vbNewLine & _
4 [NETHBO] & vbNewLine & [NETFEDPR] & vbNewLine & _
5 [NETFEDNP] & vbNewLine & [NETFEDHBP]
6 End Function
Better Solution
Arrays to the rescue! Using a nested array,
we can test values and only use valid ones in
our label. We can also color the fonts of
specific attributes, perform calculations using
attribute values and place the results into our
label array. By using a point featureclass
(converted from the polygon featureclass) to
label with, we have control over the absolute
position of the labels around the points.
Partial Solution
Sure, we can stack the labels with a simple
FindLabel function, but the results aren’t pretty at all.
Values of zero
get omitted
from the array
1 Function FindLabel ( [Well_Name] )
2
3 FindLabel = ParseWellName([Well_Name])
4
5 End Function
6
7 Function ParseWellname(well_name)
8
9 Dim patt, reg_exp, repl
10 Set reg_exp = New RegExp
11 reg_exp.IgnoreCase = False
12 ' Regex to look for commingle, tubing, casing, inactive,
13 ' not active zones that we don't want labeled
14 patt = "(\s)" & _
15 "(C*\s*T*\s*\(?CM\)?\s*(\(Inactive\)$|\(Not Active\))*$|" & _
16 "C\s*(\(Inactive\)$|\(Not Active\))*$|" & _
17 "T\s*(\(Inactive\)$|\(Not Active\))*$|" & _
18 "UT \(Inactive\)$|" & _
19 "LT \(Inactive\)$|" & _
20 "(\(Inactive\)$|\(Not Active\))*$|" & _
21 "C$|T$|UT$|LT$|" & _
22 "(\(?CM\)?$|T|C)* \(?CM\)?$|" & _
23 "[C|CM|T|LT|UT]*\s*STORAGE$)"
24
25 reg_exp.Pattern = patt
26 repl = ""
27
28 result = reg_exp.Replace(well_name, repl)
29
30 ParseWellname = result
31
32 End Function
The Problem
Here we have a point dataset (gas wells, to be more precise) where multiple
points in the same location are stacked and each one has a different label. Each
record for one well (some have only one point, some have 4+) represents a
producing zone. The well names are almost identical except for codes that denote
what zone the well is producing from. We need to get rid of the multiple labels, yet
still show all of the points.
The Solution
Let’s put a regular expression to work for us. Regular expressions
provide us with a way of identifying characters, words, or patterns
of characters in strings of text. They are similar to wildcards,
except much more powerful. For our label to work, you must have
access to the Maplex labeling engine, since we are going to use
the “Remove duplicate labels” function of the Maplex engine.
Here’s how this works:
a) Look at the attribute table for this dataset (upper right). Notice
that well names are all based off of the lease name and have the
zone information appended to that. If we can make the well names
all the same, then the Maplex “Remove duplicates” function will
remove all but one of those labels – with no modification of our
underlying dataset!
b) Our regular expression looks for the common combinations that
occur in the zone names at the end of the well names (“CM”, “UT”,
“Not Active”, etc.) and simply replaces those occurrences with “” -
nothing.
c) Now that our labels are all the same for any one well with
multiple zones, Maplex drops the duplicates and leaves us just one
– exactly what we wanted.
For more information about using regular expressions with VBScript, see http://msdn.microsoft.com/en-us/library/ms974570.aspx or Google “regular expressions vbscript”.
I also find the book VBScript in a Nutshell (published by O’Reilly, ISBN# 0596004885) to be an indispensable resource for writing FindLabel functions in VBScript.
2 Place it in the
label expression
4 Set duplicate label
search tolerance
1 Write the FindLabel
function
3 Remove duplicate
labels
1 Write the FindLabel
function
2 Place it in the
label expression
3 Absolutely position
label around point
BEFORE
BEFORE
AFTER
AFTER
DISCLAIMER: Not the
most efficient or elegant
regular expression, but
it works