Proper case for ordinals using Arcade

6911
10
Jump to solution
11-15-2019 12:35 PM
JeffThomasILM
Occasional Contributor II

The Proper() Arcade function is a handy way to format street names that are stored in all caps. But it doesn't deal with ordinals correctly; e.g., returns "16Th" rather than "16th". There are a few different ways to handle this in Python but I don't know how to do it with Arcade. Any ideas? Thanks!

Tags (2)
0 Kudos
1 Solution

Accepted Solutions
CiaraRowland-Simms
Esri Contributor

You also might find the number function useful to check if the first character is numeric. It returns NaN if the value you give it is not a number, then IsNan can be used to check it. Something along these lines should satisfy a lot of the issues you mention:

var words = split($feature.NAME, " ")
var label = ""
for (var i in words)
   label += iif(IsNan(number(words[0])), proper(words), lower(words)) + " "
return label

Although it won't give you correct capitalization in all cases e.g. James Ii Street instead of James II Street or Virginia Avenue Sw instead of Virginia Avenue SW . 

View solution in original post

10 Replies
KoryKramer
Esri Community Moderator

Ciara Rowland-Simms‌ demo'd this in  Video Link : 5618 .  Check out around 49 min 30 sec through 51 minutes...

JeffThomasILM
Occasional Contributor II

Thanks for the video link; that heads me in the right direction. However, it doesn't totally fix the problem. Her example explicitly lists out 1ST, 2ND, etc. rather than a regex or some Arcade equivalent to Python's isalphacapitalize, or capwords methods that handle any ordinals. I wondered if it would work on two or more digit ordinals and I verified it doesn't with my own data. I thought maybe I made a typo when copying her function, but I even went back and watched the video again to see if any two-digit ordinal streets are visible: Yep, and indeed the case wasn't fixed in her own example (see "12Th" below). I laughed a little when she said, "I can write quite a simple expression to fix that." It's 10 times longer than the Python solution, which actually handles any ordinal! Surely there's a better way to do this with Arcade?

0 Kudos
JeffThomasILM
Occasional Contributor II

Sorry for being a bit insensitive above; I'm very appreciative of all the help I've received here on GeoNet. I am always trying to cobble together solutions with my limited knowledge of Python and now Arcade. I get exasperated with the transition occasionally. I imagine Ciara's expression would in fact work as intended with a tweak or two. But the bigger issue is that I suspect Arcade simply doesn't have methods equivalent to Python's for this problem (yet). That makes sense; Python's been around many years and Arcade is still pretty new. Seeing as Arcade is entirely under Esri's control, there are many map-specific issues that should be incorporated into Arcade. For instance, wouldn't it be nice if Proper() simply handled case properly including ordinals? Is there some reason that would be undesirable or unworkable? Thanks.

0 Kudos
CiaraRowland-Simms
Esri Contributor

Hi Jeff, 

Sorry to hear it wasn't quite what you were looking for. Unfortunately only realized after the video meetup that I had missed off a couple of cases ("1TH", "2TH", "3TH") from my list of ordinals! I believe adding those in should fix the street name issues.

Proper case has quite a specific meaning in Arcade in that it capitalizes the first letter. This means in cases such as

proper("[hello]")

Arcade will return [Hello], even though the first character is a square bracket.  It moves on to the second character to make it proper case. By contrast, the capitalize function in Python will leave this lower case. If that is the behavior you are looking for, this can be done in Arcade by explicitly pulling out the first character with something along the lines of:

var words = split($feature.NAME, " ")
var label = ""
for (var i in words)
   label += upper(words[0]) + lower(right(words, count(words) - 1)) + " "
return label

However, this will not handle special cases such as if you have a leading bracket or quote, or if you require upper case compass directions. Those would also not be handled by Python's capitalize function. Indeed the StackOverflow article you mentioned includes regex examples which are looking for a digit followed by th, st etc. to handle the ordinals special case. Unfortunately the capitalization of street names can be fiddly. Regex is certainly an option in Python, but it is important to remember that this is doing the same work, just in a single (although often less readable) line. 

While it is true that the proper() function could internally look out for and have special behavior to handle these known cases, Arcade is used globally, and not all languages share the same set of special cases. A special case for one user may not work for another. Writing these longer expressions for specific cases can take a little getting used to, but ultimately is what you are doing even in Python via regex.

I hope that helps! Let me know if you have any more questions.

All the best,

Ciara

CiaraRowland-Simms
Esri Contributor

You also might find the number function useful to check if the first character is numeric. It returns NaN if the value you give it is not a number, then IsNan can be used to check it. Something along these lines should satisfy a lot of the issues you mention:

var words = split($feature.NAME, " ")
var label = ""
for (var i in words)
   label += iif(IsNan(number(words[0])), proper(words), lower(words)) + " "
return label

Although it won't give you correct capitalization in all cases e.g. James Ii Street instead of James II Street or Virginia Avenue Sw instead of Virginia Avenue SW . 

JeffThomasILM
Occasional Contributor II

Thank you, Ciara. I know there's no one perfect solution, but you're giving exactly what I had hoped for: examples of how to apply similar methods/logic of Python in Arcade. 

JeffThomasILM
Occasional Contributor II

Here's the code I'm currently using, in case it's helpful to anyone. There are many nulls and blanks in our street names (I have no control over them) so I have to filter those out first (as covered in this thread), as well as concatenate the other pieces of the street name. I added one special condition to look for, "Mc", and could add more using this if-else logic if needed. Fortunately, I don't have many street names needing special formatting like Ciara mentioned.

if (!IsEmpty($feature.STREET)) {
  var words = split($feature.STREET, " ")
  var dir = proper(DomainName($feature, 'DIR'))
  var street = ""
  var type = proper(DomainName($feature, 'TYPE'))
  for (var i in words) 
    if (left(words[i], 2) == "MC") {
      street += upper(words[i][0]) + lower(words[i][1]) + upper(words[i][2]) + lower(right(words[i], count(words[i]) - 3)) + " ";
    } else {
      street += iif(IsNan(number(words[i][0])), proper(words[i]), lower(words[i])) + " ";
    }
  return concatenate([dir, street, type]," ");
} else { return "";
}

Thanks again to Kory and Ciara.

JeffThomasILM
Occasional Contributor II

I discovered there are also some "Mac" street names to consider, too. The problem is, "Mac" can't be called out universally like "Mc", so I have to have a whitelist of "Mac" names to consider. I am now using every bit of new Arcade logic that I've learned from Ciara on this thread.

I got it to work with most of the Mac names I have, except one problem: it doesn't work if "Mac" is preceded by another word, e.g. "Old MacCumber Station". The resulting label is just "Station". I'm afraid I've tapped out my Arcade knowledge... do you know what I need to change for it to work properly? Thank you!

if (!IsEmpty($feature.STREET)) {
  var words = split($feature.STREET, " ")
  var Mac = ["MACCUMBER", "MACKAY", "MACKENZIE", "MACMILLAN", "MACRAE"]
  var dir = proper(DomainName($feature, 'DIR'))
  var street = ""
  var type = proper(DomainName($feature, 'TYPE'))
  for (var i in words) 
    var isMac = indexof(Mac, words[i]) > -1
    if (left(words[i], 2) == "MC") {
      street += upper(words[i][0]) + lower(words[i][1]) + upper(words[i][2]) + lower(right(words[i], count(words[i]) - 3)) + " ";
    } else if (isMac) {
      street += upper(words[i][0]) + lower(words[i][1]) + lower(words[i][2]) + upper(words[i][3]) + lower(right(words[i], count(words[i]) - 4)) + " ";
    } else {
      street += iif(IsNan(number(words[i][0])), proper(words[i]), lower(words[i])) + " ";
    }
  return concatenate([dir, street, type]," ");
} else {
  return "";
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Side note to the Esri community webmaster: shouldn't Arcade be added to the the code block language options?

0 Kudos
CiaraRowland-Simms
Esri Contributor

Hi Jeff,

I can't see anything obviously wrong with your script that would cause you to only get the last word. The search could fail however your street field is not all capitalized since the search is case sensitive on line 8, so you might want to consider changing line 2 to: var words = split(upper($feature.STREET), " ")

When I run the script in the playground and return the street it seems to come out okay, so not sure what's going wrong. Could you provide any more info?

0 Kudos