Advanced labeling in ArcMap with VBScript FindLabel functions

05d9c2908ca26febd6e0caa0f270f65b?s=47 Chad Cooper
September 25, 2008
300

Advanced labeling in ArcMap with VBScript FindLabel functions

05d9c2908ca26febd6e0caa0f270f65b?s=128

Chad Cooper

September 25, 2008
Tweet

Transcript

  1. 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, "<clr red='255'>", " CNT</clr>"), _ 60 Array(mathNP, "<clr red='0' green='0' blue='0'>", " NP</clr>"), _ 61 Array(mathHBP, "<clr red='0' green='0' blue='0'>", " HBP</clr>"), _ 62 Array(mathPR, "<clr red='0' green='0' blue='0'>", " PR</clr>"), _ 63 Array(fednp, "<clr red='132' green='0' blue='168'>", " FNP</clr>"), _ 64 Array(fedpr, "<clr red='132' green='0' blue='168'>", " FPR</clr>"), _ 65 Array(fedhbp, "<clr red='132' green='0' blue='168'>", " FHBP</clr>")) 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 = "<bol>" 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 & "</bol>" & 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</clr>", totnetac, "<clr red='255'>"), _ 110 Array(" NP</clr>", netnp, "<clr red='0' green='0' blue='0'>"), _ 111 Array(" PR</clr>", netpr, "<clr red='0' green='0' blue='0'>"), _ 112 Array(" HBP</clr>", nethbp, "<clr red='0' green='0' blue='0'>"), _ 113 Array(" HBO</clr>", hbo, "<clr red='0' green='0' blue='0'>")) 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 = "<bol>" 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 & "</bol>" & 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