Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Advanced labeling in ArcMap with VBScript FindLabel functions

Chad Cooper
September 25, 2008
460

Advanced labeling in ArcMap with VBScript FindLabel functions

Chad Cooper

September 25, 2008
Tweet

More Decks by Chad Cooper

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, "", " 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

    View full-size slide