<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>writing as affiliative behavior</title>
<link>https://feralcatcolonist.github.io/</link>
<atom:link href="https://feralcatcolonist.github.io/index.xml" rel="self" type="application/rss+xml"/>
<description>a spatial and programming blog</description>
<generator>quarto-1.10.19</generator>
<lastBuildDate>Wed, 07 Oct 2026 00:00:00 GMT</lastBuildDate>
<item>
  <title>[R] You Ready, Kids?</title>
  <link>https://feralcatcolonist.github.io/posts/2026-10-07_r-you-ready-kids/</link>
  <description><![CDATA[ 






<p>I try not to be a competitive person. Most of the time I’m fairly successful, I can focus on me and let other things lie. Unfortunately, there are a few situations that tend to goad me into action; creating, what some might term a, <em>hold my beer</em> moment. Mostly these are some flavor of: &gt; item/action/technique doesn’t work in <code>&lt;insert thing here&gt;</code> A few weeks ago, I was whisked down into the depths of single-minded competition (with no one actually except the shade of some gatekeeper). In the GIS Discord, it started with a stray comment: &gt; “I found a non-esri result, but it bluntly told me that you can’t do R in arcgis notebooks” My immediate response was: &gt; Who wants to use a filthy ArcGIS notebook anyway? Literally the worst way to jupyter <sup></sup>^ and as petty as my comment was, I was already hooked.</p>
<section id="absorbent-and-yellow-and-porous-is-he" class="level2">
<h2 class="anchored" data-anchor-id="absorbent-and-yellow-and-porous-is-he">absorbent and yellow and porous is he</h2>
<p>It turned over and over in my mind’s eye. <em>I doubt it’s impossible.</em> But I had just read a bunch of documentation. <em>Squints.</em> I have a Mendela Effect memory of an ArcGIS Notebook having a kernel switch button for R.</p>
<p>While there isn’t an out-and-out statement that, “you can’t use R inside of an ArcGIS Notebook in ArcGIS Pro”, everything is described like: - do your analysis in RStudio, then bring your results into ArcGIS Pro - <code>arcpy</code> can be accessed from R using the <code>reticulate</code> - R-based GP script tools are defined in a standalone R script file I mean, it is called the R-ArcGIS Bridge. A bridge, <em>bridges</em>, something. A gap? Gulf? Chasm? It implies discrete destinations. Depending on where you have lived, where you were raised, your mental image of a bridge might be big. I’m from Texas. We have some big-er (not everything bigger in Texas, that just Texan propaganda) bridges. I was raised near an artifical reservoir flooded during the 1930s. I have many memories of the very long drive across the bridge. Looking at the level of the lake to determine how bad the drought is. Or how relieved we are that the lake is full.</p>
<p>But when I close my eyes. It’s a looney tunes style bridge. I watched a lot of cartoons. When I push past that visualization, I see a foot bridge. Spanning a stream you could hop over. What’s the point? So your feet don’t get wet, I guess. Me? I have lots of memories of jumping over little streams (sometimes unsuccessfully, muddy antisc ensue).</p>
</section>
<section id="lets-get-our-feet-wet" class="level2">
<h2 class="anchored" data-anchor-id="lets-get-our-feet-wet">let’s get our feet wet</h2>
<p>and let’s be honest. If this were something that wasn’t considered impossible, there’d be some kind of support article about it. Surely a small blurb like, you can use it in an ArcGIS Pro Notebook! We’re on the main stage, Jack has some clever intro like, R you ready kids!? The crowd chants enthusiastically back, “AYE AYE CAPTAIN!”</p>
</section>
<section id="incrementing-number-of-features" class="level2">
<h2 class="anchored" data-anchor-id="incrementing-number-of-features">Incrementing Number of Features</h2>
<p>At previous organizations, I’ve done some fancy things with AssetIDs. And by fancy, I mean dumb. And by dumb, I mean, over-engineered based on spatial positioning for a grid system that was subject to change. I really like the pattern our staff came up with, it bakes in a create date, who collected it, and then doesn’t artificially constrain the total number of assets– –just assumes a person doesn’t collect more than 999 assets in a day. How does one increment features though? Through featuresets of course! But before we get to featuresets, it’s helpful to thing about how we’d do this on the desktop.</p>
<section id="filter" class="level3">
<h3 class="anchored" data-anchor-id="filter">Filter</h3>
<p>We would Filter our feature class using a definition query. Arcade utilizes the SQL-92 standard, which is the same used by shapefiles and file geodatabases. Some light reading can be found here:</p>
<p>SQL reference for query expressions used in ArcGIS—ArcGIS Pro | Documentation <a href="https://doc.esri.com/en/arcgis-pro/latest/help/mapping/navigation/sql-reference-for-elements-used-in-query-expressions.html">https://doc.esri.com/en/arcgis-pro/latest/help/mapping/navigation/sql-reference-for-elements-used-in-query-expressions.html</a></p>
</section>
<section id="references" class="level3">
<h3 class="anchored" data-anchor-id="references">References</h3>
<p><a href="https://www.sciencedirect.com/science/article/pii/S1364815223002906">https://www.sciencedirect.com/science/article/pii/S1364815223002906</a></p>


</section>
</section>

 ]]></description>
  <category>ArcGIS</category>
  <category>R</category>
  <category>Conda</category>
  <guid>https://feralcatcolonist.github.io/posts/2026-10-07_r-you-ready-kids/</guid>
  <pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate>
  <media:content url="https://feralcatcolonist.github.io/img/posts/2024-12-27-arcade-machine.png" medium="image" type="image/png" height="81" width="144"/>
</item>
<item>
  <title>Arcade - Create a Unique Asset ID</title>
  <link>https://feralcatcolonist.github.io/posts/2024-12-27_arcade-unique-asset-id/</link>
  <description><![CDATA[ 






<p>For me, when I’m writing something I like to have the different portions/objectives laid out so that I make sure I’m thinking about everything that I need to. This also helps me to size out my steps because I like to be able to look at everything at a glance. For this project, the goal was as follows, create a unique Asset Identifier like the following:</p>
<p><code>DRS_JC_20241223_001</code></p>
<p>Where we meet the following criteria:</p>
<ol type="1">
<li>the initial section is the feature class prefix</li>
<li>the second section is the initials of the user</li>
<li>the third section is an 8-digit date like <code>YYYMMDD</code></li>
<li>the fourth section is an incrementing number of the features collected that match: <em>the current user, the current date, then add one (1)</em></li>
</ol>
<p>When we lay it out like this, the code is fairly unassuming; if we used a template literal, it would look kinda like:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb1-1"><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>prefix<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>user_initials<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>todaysdate<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>some_increment<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span></span></code></pre></div></div>
<p>So now we get to ask ourselves, how would we go about getting each of these pieces of information? And might there be some hidden pieces of logic we’re forgetting about?</p>
<section id="prefix" class="level2">
<h2 class="anchored" data-anchor-id="prefix">Prefix</h2>
<p>This is more manual than I would actually prefer. I would love to simply ask the feature class what its name is and dynamically enter it but there is not yet any functionality for that. Since we’re using this from the Field Maps Designer we, as the author, know what feature class we’re working with.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> fc_prefix <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"DRS"</span></span></code></pre></div></div>
<p><em>So we’ll enter this manually, that’s okay I guess</em></p>
</section>
<section id="user-initials" class="level2">
<h2 class="anchored" data-anchor-id="user-initials">User Initials</h2>
<p>Creating this variable is most definitely dependent on how your users are entered into your ArcGIS Online organization or Enterprise Portal. The assumption is that you have some derivation of: <code>firstname.lastname</code></p>
<p>Standardization is great. And sometimes you have to take an opportunity to implement it, like an automation for an AssetID.</p>
<p>I digress. Okay, we’re looking for initials, if I were working in Python I’d just do some string slicing here. In Arcade slicing is for arrays, so for text/string we work with syntax like <code>Left</code>, <code>Mid</code>, or <code>Right</code>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> first_initial <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">LEFT</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"firstname.lastname"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span></code></pre></div></div>
<p>In order to isolate the last name, we’ll use the <code>Split</code> function on a known identifier, which would be the full stop “.”</p>
<p><code>Split</code> returns the text as an array, so we’ll select the second index position <code>[1]</code>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> split_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">SPLIT</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"firstname.lastname"</span>) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ["firstname", "lastname"]</span></span>
<span id="cb4-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> last_name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> split_names[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// "lastname"</span></span>
<span id="cb4-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> last_initial <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">LEFT</span>(last_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// "l"</span></span></code></pre></div></div>
<p>Arcade can be kind of wordy if you let it be. You can combine all of those steps but eventually readability becomes an issue.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> last_initial <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Left</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Split</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"firstname.lastname"</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span></code></pre></div></div>
<p>While one-liners work, sometimes they are a pain to maintain. We can keep it as a single variable and add some more legible structure to it.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> last_initial <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Left</span>(</span>
<span id="cb6-2">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Split</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"firstname.lastname"</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb6-3">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span></code></pre></div></div>
<p>And yeah, we totally got ahead of ourselves. How do we even get the username programmatically? With a Portal function, <code>GetUser</code>. It’s super neat and you can use it to do permissions management. Anyway, it returns a dictionary with the following values:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../img/posts/2024-12-27-arcade-doc-snippet-01.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Portal functions | ArcGIS Arcade | Esri Developer"><img src="https://feralcatcolonist.github.io/img/posts/2024-12-27-arcade-doc-snippet-01.png" class="img-fluid figure-img" alt="return values for the GetUser Portal function within ArcGIS Arcade"></a></p>
<figcaption>Portal functions | ArcGIS Arcade | Esri Developer</figcaption>
</figure>
</div>
<p>Our goal is to be able to do some things offline, so we are dependent on the username attribute only. As dictionary values, these can be accessed with dot-notation. The portal object is the current layer we’re working with which we can use the $layer global variable. But it could be any kind of Portal object if you know the itemID or its name.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> user_name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">GetUser</span>($layer)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">username</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// "firstname.lastname"</span></span></code></pre></div></div>
<p>Now that we have a method for getting the username we can put it all together like:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> user_name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">GetUser</span>($layer)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">username</span></span>
<span id="cb8-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> user_initials <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Upper</span>(</span>
<span id="cb8-3">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Left</span>(user_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-4">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Left</span>((<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Split</span>(user_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"."</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb8-5">)</span></code></pre></div></div>
<p><em>I added an UPPER to keep the initials looking tidy</em></p>
</section>
<section id="digit-date" class="level2">
<h2 class="anchored" data-anchor-id="digit-date">8-Digit Date</h2>
<p>Thanks to some convenience functions this one is fairly simple. We can use Today to grab today’s date. That will return a date object, we want to format it, so we’ll head over to the text functions for that piece.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../img/posts/2024-12-27-arcade-doc-snippet-01.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Text functions | ArcGIS Arcade | Esri Developer"><img src="https://feralcatcolonist.github.io/img/posts/2024-12-27-arcade-doc-snippet-01.png" class="img-fluid figure-img" alt="Screenshot of the Arcade TEXT function"></a></p>
<figcaption>Text functions | ArcGIS Arcade | Esri Developer</figcaption>
</figure>
</div>
<p>Text will take any value and convert it into a text value, it has an optional format parameter that we’ll use to specify how we want our date to be outputted; the eight-digit pattern looks like: “YMMDD”</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb9-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> date_eight_digits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Text</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Today</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"YMMDD"</span>)</span></code></pre></div></div>
<p><em>This is definitely less confusing than in Python</em></p>
</section>
<section id="incrementing-number-of-features" class="level2">
<h2 class="anchored" data-anchor-id="incrementing-number-of-features">Incrementing Number of Features</h2>
<p>At previous organizations, I’ve done some fancy things with AssetIDs. And by fancy, I mean dumb. And by dumb, I mean, over-engineered based on spatial positioning for a grid system that was subject to change. I really like the pattern our staff came up with, it bakes in a create date, who collected it, and then doesn’t artificially constrain the total number of assets– –just assumes a person doesn’t collect more than 999 assets in a day. How does one increment features though? Through featuresets of course! But before we get to featuresets, it’s helpful to thing about how we’d do this on the desktop.</p>
<section id="filter" class="level3">
<h3 class="anchored" data-anchor-id="filter">Filter</h3>
<p>We would Filter our feature class using a definition query. Arcade utilizes the SQL-92 standard, which is the same used by shapefiles and file geodatabases. Some light reading can be found here:</p>
<p>SQL reference for query expressions used in ArcGIS—ArcGIS Pro | Documentation <a href="https://doc.esri.com/en/arcgis-pro/latest/help/mapping/navigation/sql-reference-for-elements-used-in-query-expressions.html">https://doc.esri.com/en/arcgis-pro/latest/help/mapping/navigation/sql-reference-for-elements-used-in-query-expressions.html</a></p>
<p>Again, pretending we’re just writing a definition query on the desktop, because we would know what field we’re searching, the AssetID, we’d know what feature class we were in “DRS”, we would know what worker we’re looking for “JC”, and we’d know today’s date “20241227”. What we wouldn’t necessarily know, is how many records we were expecting to find, so we’d use the modulo “%” as a wildcard. We’d write something like:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb10-1">AssetID <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">LIKE</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'DRS_JC_20241227_%'</span></span></code></pre></div></div>
<p>In our work so far, we’ve already created all of these pieces of information as variables. Our SQL Query looks like this:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb11-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> sql_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">`AssetID LIKE '</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>prefix<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>user_initials<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>date_eight_digits<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_%'`</span></span></code></pre></div></div>
<p>This looks suspiciously like what we already saw at the top of the article. Where does <code>FILTER</code> come in? With featuresets. Featuresets deserve their own article to be honest. We’ll do our best to gloss over it. Your <code>$layer</code> is a featureset. End gloss. So if we have a featureset, we can then filter it with a standard SQL-92 query. This returns a smaller featureset of features that match our filter.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb12-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> current_features <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Filter</span>($layer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> sql_query)</span></code></pre></div></div>
</section>
<section id="count-number-of-features" class="level3">
<h3 class="anchored" data-anchor-id="count-number-of-features">Count Number of Features</h3>
<p>Now that we know how many features match our formula, let’s count how many there are and then one (1) to that. This is made simple with Count, it returns a number, for our featureset it could be any integer from zero (0) upwards. In our example, we’ll just add one (1) to it.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb13-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> feature_count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Count</span>(current_features) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span></code></pre></div></div>
<p>A problem that we’ll run into though, is this number isn’t padded. If we keep it as-is, then our Asset IDs will end up looking like this:</p>
<pre><code>DRS_JC_20241223_1
DRS_JC_20241223_10
DRS_JC_20241223_100
...
DRS_JC_20241223_9
DRS_JC_20241223_99
DRS_JC_20241223_999</code></pre>
<p>Since they’re a text column they won’t sort right. Everyone will hate it. We’ll again turn back to the ever-useful Text to format this appropriately:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb15-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> feature_count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Text</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Count</span>(current_features) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'000'</span>)</span></code></pre></div></div>
</section>
<section id="calculate-assetid" class="level3">
<h3 class="anchored" data-anchor-id="calculate-assetid">Calculate AssetID</h3>
<p>We’re going to wrap this all together as a function to keep it tidy. And also, because there’s a gotcha coming up ahead. There are all sorts of things we could discuss about functions but we’re going to also gloss over it. For right now, it’s enough to say, you have to create a function first before you can use it. Our function takes some parameters, calculates the things from above, and returns a string that matches our incrementing asset ID pattern.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb16-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Calculate_Asset_ID</span>(fp_prefix<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fp_user_initials<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fp_date_eight_digits) {</span>
<span id="cb16-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> sql_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">`AssetID LIKE '</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>fp_prefix<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>fp_user_initials<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>fp_date_eight_digits<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_%'`</span></span>
<span id="cb16-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> current_features <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Filter</span>($layer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> sql_query)</span>
<span id="cb16-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> feature_count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Text</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Count</span>(current_features) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'000'</span>)</span>
<span id="cb16-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>fp_prefix<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>fp_user_initials<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>fp_date_eight_digits<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>feature_count<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span></span>
<span id="cb16-6">}</span></code></pre></div></div>
<p><em>nice and tidy</em></p>
</section>
</section>
<section id="hidden-pieces-of-logic" class="level2">
<h2 class="anchored" data-anchor-id="hidden-pieces-of-logic">Hidden Pieces of Logic</h2>
<p>I think that this is the whole reason why I wanted to write about this anyway. When I originally wrote this Arcade snippet, it did not have a test to check if the Asset ID already existed. Which meant, any time any asset was updated, it got a new Asset ID with a new date and the user information of the person who edited it. What a mess. So what we needed to add was a final test to check if the Asset ID was currently empty:</p>
<ul>
<li>return a new Asset ID if it was empty</li>
<li>return the existing Asset ID if it was not empty</li>
</ul>
<p>The easiest way to craft a test like this is with <code>When</code>, it takes some logic and returns a corresponding value, and a default value if it fails all of the tests. Our test will be to access the current feature’s Asset ID attribute. We can do this with <code>IsEmpty</code>, the global variable <code>$feature</code>, and some dot-notation.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb17-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">When</span>(</span>
<span id="cb17-2">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">IsEmpty</span>($feature<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">AssetID</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> True</span>
<span id="cb17-3">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// the value if true</span></span>
<span id="cb17-4">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// the default value</span></span>
<span id="cb17-5">)</span></code></pre></div></div>
<p>So we can tuck this into a variable assignment like this:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb18-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> asset_id <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">When</span>(</span>
<span id="cb18-2">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">IsEmpty</span>($feature<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">AssetID</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> True<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Calculate_Asset_ID</span>(fc_prefix<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> user_initials<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> date_eight_digits)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb18-3">    $feature<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">AssetID</span></span>
<span id="cb18-4">)</span></code></pre></div></div>
<p><em>I like having the logic test and result on the same line</em></p>
<p>An added benefit here is that we’re only accessing our featureset and running the calculations if it is actually needed– –this can slow down your workflow if the underlying feature class is very large.</p>
<p>And that’s it. When we put all the pieces back together we’re left with a pretty readable Arcade snippet that bakes in our requirements for a unique, iterable Asset ID, that records the created date, person who captured the data, and that is usable within the Field Maps Designer application.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb19-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Calculate_Asset_ID</span>(fp_prefix<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fp_user_initials<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fp_date_eight_digits) {</span>
<span id="cb19-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> sql_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">`AssetID LIKE '</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>fp_prefix<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>fp_user_initials<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>fp_date_eight_digits<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_%'`</span></span>
<span id="cb19-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> current_features <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Filter</span>($layer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> sql_query)</span>
<span id="cb19-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> feature_count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Text</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Count</span>(current_features) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'000'</span>)</span>
<span id="cb19-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>fp_prefix<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>fp_user_initials<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>fp_date_eight_digits<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>feature_count<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span></span>
<span id="cb19-6">}</span>
<span id="cb19-7"></span>
<span id="cb19-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> fc_prefix <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"DRS"</span></span>
<span id="cb19-9"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> user_name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">GetUser</span>($layer)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">username</span></span>
<span id="cb19-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> user_initials <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Upper</span>(</span>
<span id="cb19-11">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Left</span>(user_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb19-12">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Left</span>((<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Split</span>(user_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"."</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb19-13">)</span>
<span id="cb19-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> date_eight_digits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Text</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Today</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"YMMDD"</span>)</span>
<span id="cb19-15"></span>
<span id="cb19-16"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> asset_id <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">When</span>(</span>
<span id="cb19-17">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">IsEmpty</span>($feature<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">AssetID</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> True<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Calculate_Asset_ID</span>(fc_prefix<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> user_initials<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> date_eight_digits)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb19-18">    $feature<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">AssetID</span></span>
<span id="cb19-19">)</span>
<span id="cb19-20"></span>
<span id="cb19-21"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> asset_id</span></code></pre></div></div>
<p><em>Looking at it now, I kind of hate it.</em></p>
<p>Why don’t we just shove everything in the function? I mean, if we only need to calculate these things sometimes, why are we calculating those other things all the time?</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb20-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Calculate_Asset_ID</span>() {</span>
<span id="cb20-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> fc_prefix <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"DRS"</span></span>
<span id="cb20-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> user_name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">GetUser</span>($layer)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">username</span></span>
<span id="cb20-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> user_initials <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Upper</span>(</span>
<span id="cb20-5">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Left</span>(user_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb20-6">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Left</span>((<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Split</span>(user_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"."</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb20-7">    )</span>
<span id="cb20-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> date_eight_digits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Text</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Today</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"YMMDD"</span>)</span>
<span id="cb20-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> sql_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">`AssetID LIKE '</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>fc_prefix<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>user_initials<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>date_eight_digits<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_%'`</span></span>
<span id="cb20-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> current_features <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Filter</span>($layer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> sql_query)</span>
<span id="cb20-11">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> feature_count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Text</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Count</span>(current_features) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'000'</span>)</span>
<span id="cb20-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>fc_prefix<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>user_initials<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>date_eight_digits<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">${</span>feature_count<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span></span>
<span id="cb20-13">}</span>
<span id="cb20-14"></span>
<span id="cb20-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> asset_id <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">When</span>(</span>
<span id="cb20-16">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">IsEmpty</span>($feature<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">AssetID</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> True<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Calculate_Asset_ID</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb20-17">    $feature<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">AssetID</span></span>
<span id="cb20-18">)</span>
<span id="cb20-19"></span>
<span id="cb20-20"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> asset_id</span></code></pre></div></div>
<p><em>That’s probably better</em></p>
<p>Since we’re doing everything inside of the function, we removed all the parameters. However, an argument could be made for some new parameters. The global variable <code>$layer</code> is doing a lot of work– –it’s acting as a Portal object for getting the username AND it is also returning the featureset of interest. Probably you’d want to separate those concerns.</p>
<p>I can’t unsee it. I put it in the gist.</p>
<p>Here’s the gist: ArcGIS Arcade - Construct a unique ID like: DRS_JC_20241227_001 <a href="https://gist.github.com/FeralCatColonist/a6d4f5a299dd9a5443687ed998fbb73f" class="uri">https://gist.github.com/FeralCatColonist/a6d4f5a299dd9a5443687ed998fbb73f</a></p>


</section>

 ]]></description>
  <category>Arcade</category>
  <category>AGOL</category>
  <guid>https://feralcatcolonist.github.io/posts/2024-12-27_arcade-unique-asset-id/</guid>
  <pubDate>Fri, 27 Dec 2024 00:00:00 GMT</pubDate>
  <media:content url="https://feralcatcolonist.github.io/img/posts/2024-12-27-arcade-machine.png" medium="image" type="image/png" height="81" width="144"/>
</item>
<item>
  <title>Command Line-ception</title>
  <link>https://feralcatcolonist.github.io/posts/2024-12-18_command-line-ception/</link>
  <description><![CDATA[ 






<section id="sorry-theres-no-jump-to-recipe-button-the-context-is-a-bit-important" class="level2">
<h2 class="anchored" data-anchor-id="sorry-theres-no-jump-to-recipe-button-the-context-is-a-bit-important"><em>sorry there’s no jump to recipe button, the context is a bit important</em></h2>
<p>This latest project happened a bit unexpectedly. I was finishing up our shop’s deployment of the PowerShell DSC for ArcGIS and was excited to set up the disaster recovery parameters handled by the WebGIS DR tool. The WebGIS DR tool is a command line interface (CLI) that has been packaged with ArcGIS Enterprise (colloquially known as Portal) since at least the 10.5 version. This takes a backup of the ArcGIS Enterprise in a variety of styles: <code>backup</code>, <code>full</code>, and <code>incremental</code>.</p>
<p>But this begs the question, if the purpose of a disaster recovery tool is to revert to a state of normalcy from some <em>unexpected</em> disaster, then should our disaster recovery be automated? The answer is yes. And you could easily set this as an automation with a simple <code>.bat</code> file if you only wanted to have a single type of backup. You’d write something like this, assuming you’re not wanting to put in additional files inside of the ArcGIS Enterprise installation paths:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode cmd code-with-copy"><code class="sourceCode dosbat"><span id="cb1-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"C:\Program Files\ArcGIS\Portal\tools\webgisdr"</span></span>
<span id="cb1-2">webgisdr -<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-export</span> -<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-file</span> webgisdr.properties</span></code></pre></div></div>
<p>It’s simple. Which is really part of the problem. There is not a flag for the kind of backup you want to run. That information is stored on <code>line 28</code> of the <code>webgisdr.properties</code> files, in a parameter that looks like this:</p>
<pre><code># Specify the Web GIS backup restore mode: backup, full or incremental. Default is backup.
BACKUP_RESTORE_MODE = BACKUP</code></pre>
<p>And if you’re not really steeped in backup modes, you might be wondering why there are three kinds, and maybe why you’d want each one.</p>
<ul>
<li><p><strong>Backup</strong> - This is the WebGIS DR tool’s default setting. It is a regular copy. The same kind of copy/paste type deal everyone is duly familiar with.</p></li>
<li><p><strong>Full</strong> - This is the same as a backup. However, this also allows you to access the third type of backup, incremental.</p></li>
<li><p><strong>Incremental</strong> - This is a backup that can be used only after saving a full backup. It only captures data from after the full backup. The advantage is this file is smaller, and runs faster than either the backup or full modes.</p></li>
</ul>
<p>If you’re looking for speed of backups and want also to minimize storage size of your backups, then you’ll want to pursue a strategy utilizing full and incremental backups in tandem. Assuming for a moment that you didn’t care to run default backup, you’d really need to hold onto two copies of the webgisdr.properties file and maybe name them with suffixes like <code>_full</code> and <code>_incremental</code>.</p>
<p>Now your simple <code>.bat</code> file is two simple <code>.bat</code> files that each reference a separate <code>.properties</code> file so that you can set a single parameter differently.</p>
<pre><code>#maybe this runs every 12 hours
cd "C:\Program Files\ArcGIS\Portal\tools\webgisdr"
webgisdr --export --file webgisdr_incremental.properties</code></pre>
<pre><code>#maybe this runs every 7 days
cd "C:\Program Files\ArcGIS\Portal\tools\webgisdr"
webgisdr --export --file webgisdr_full.properties</code></pre>
<p>And on top that, you still need to prune your old backup copies. Unless storage space isn’t a concern for your organization. Which would be a cool non-problem to have.</p>
</section>
<section id="two-levels" class="level2">
<h2 class="anchored" data-anchor-id="two-levels">Two Levels</h2>
<p>This is where I had a moment of clarity. What I really wanted.</p>
<p><a href="../../img/posts/2024-12-18-cli-ception.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://feralcatcolonist.github.io/img/posts/2024-12-18-cli-ception.jpg" class="img-fluid" alt="a still from the movie inception, texas has been added to say, a command line within a commandline. two levels."></a></p>
<p>And it wasn’t 6 files (3 <code>.bat</code> files and their 3 corresponding <code>.properties</code> files).</p>
<p>It was a single file that I could schedule and then never worry about disaster recovery again… …you know, except if disaster were to strike… …and obviously a bi-annual disaster recovery exercise because you don’t want your first time testing your disaster recovery option to be when you actually have to use it to continue business operations.</p>
<p>I needed something like, a command line interface. Where I could state an option within the scheduled task and call it a day.</p>
<p>It ended up looking like this:</p>
<pre><code>#on a weekly task
WebGISDR-cli-wrapper.py -full 4</code></pre>
<pre><code>#on a daily task
WebGISDR-cli-wrapper.py -incremental 4</code></pre>
<p>With these extra options, I set the type of backup and the number of copies to retain as well.</p>
<p>And if you’re like me, the question you’re probably asking is, but how? And fortunately, that’s pretty easy to answer. I wrote a CLI wrapper in Python in about a hundred lines that really boils down to 3 functions:</p>
<ol type="1">
<li><code>prune_copies()</code> - take a directory, search for the <code>.webgissite</code> files that match the selected backup mode, and keep only the number that you want to have on hand at a given time. This results in dropping a file every time before you run the WebGIS DR tool again.</li>
<li><code>set_WebGISDR()</code> - open the <code>webgis.properties</code> file, change the parameter <code>BACKUP_RESTORE_MODE</code> parameter to match the user input</li>
<li><code>run_WebGISDR()</code> - runs the original WebGIS DR CLI like it would normally run: <code>webgisdr --export --file webgisdr.properties</code></li>
</ol>
<p>We’ll go ahead and look at it in a logical sequence:</p>
<p>This is the flow of the CLI. We take some arguments, feed those to our main function, and then distribute those to the three primary functions as needed.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> main(backup_arguments: argparse.Namespace):</span>
<span id="cb7-2">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(</span>
<span id="cb7-3">        <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\t</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">backup_mode is: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>backup_arguments<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>mode<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\t</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">copies to retain: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>backup_arguments<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>copies<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb7-4">    )</span>
<span id="cb7-5">    prune_copies(WebGISDR_backups_path, backup_arguments)</span>
<span id="cb7-6">    set_WebGISDR(WebGISDR_dir, WebGISDR_filename, backup_arguments.mode)</span>
<span id="cb7-7">    run_WebGISDR(WebGISDR_dir, WebGISDR_filename)</span>
<span id="cb7-8"></span>
<span id="cb7-9"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"__main__"</span>:</span>
<span id="cb7-10">    arguments <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> configure()</span>
<span id="cb7-11">    main(arguments)</span></code></pre></div></div>
<p>For us to ask our user to input options at runtime, we have to do some prep-work in this configure function. This was my first time using the argparse library and it was fairly painless. Looking forward to doing more with it if I need to set runtime arguments again. There’s really not a reason to ask for more from the user other than what kind of backup or the number of copies that they want to retain. There are definitely more complicated backup copy management strategies but for 80% of use-cases this is likely satisfactory.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> configure():</span>
<span id="cb8-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Configures the user input arguments</span></span>
<span id="cb8-3"></span>
<span id="cb8-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns:</span></span>
<span id="cb8-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        argparse.Namespace</span></span>
<span id="cb8-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb8-7">    parser <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> argparse.ArgumentParser()</span>
<span id="cb8-8">    parser.add_argument(</span>
<span id="cb8-9">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mode"</span>,</span>
<span id="cb8-10">        choices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"FULL"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"INCREMENTAL"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"BACKUP"</span>],</span>
<span id="cb8-11">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>.upper,</span>
<span id="cb8-12">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">help</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""the type of WebGISDR backup to be performed: backup, full, or</span></span>
<span id="cb8-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        incremental. this input is case insensitive.</span></span>
<span id="cb8-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        """</span>,</span>
<span id="cb8-15">    )</span>
<span id="cb8-16">    parser.add_argument(</span>
<span id="cb8-17">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"copies"</span>,</span>
<span id="cb8-18">        choices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>),</span>
<span id="cb8-19">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>,</span>
<span id="cb8-20">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">help</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb8-21"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        the number of copies to retain. this is the number of backups</span></span>
<span id="cb8-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        only for the backup mode input/selected. this is exclusive of the primary backup</span></span>
<span id="cb8-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        which will run at the end of the script.</span></span>
<span id="cb8-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        """</span>,</span>
<span id="cb8-25">    )</span>
<span id="cb8-26">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>spacer<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>spacer<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Running WebGISDR Automation"</span>)</span>
<span id="cb8-27">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> parser.parse_args()</span></code></pre></div></div>
<p>And for a bit of context, here’s what that would look like if you ran the –help flag.</p>
<p><em>the WebGISDR Automation CLI Wrapper’s help menu</em></p>
<pre><code>Running WebGISDR Automation
usage: WebGISDR-cli-wrapper.py [-h] {FULL,INCREMENTAL,BACKUP} {1,2,3,4,5,6,7}

positional arguments:
  {FULL,INCREMENTAL,BACKUP}
                        the type of WebGISDR backup to be performed: backup, full, or incremental. this input is case insensitive.
  {1,2,3,4,5,6,7}       the number of copies to retain. this is the number of backups only for the backup mode input/selected. this is exclusive of the        
                        primary backup which will run at the end of the script.

options:
  -h, --help            show this help message and exit</code></pre>
<p>Moving on, we can look at the imports and constant variables. For most use-cases, the only thing that should need to be filled out is the <code>WebGISDR_backups_path</code> variable. Which, should live in a machine that isn’t the Portal component. Because. Disaster.</p>
<p>You’ll also notice that the import statements are fairly light. This is designed to utilize components of the standard library in Python so that a minimal environment can be set up on the Portal machine. Also <code>pathlib</code> is just an elegant library. When I first learned Python all the file management stuff was handled using the <code>os</code> library, I’ve found that <code>pathlib</code> much easier to handle and it is more readable as well.</p>
<p><em>import statements and constant variables</em></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> argparse</span>
<span id="cb10-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb10-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> subprocess</span>
<span id="cb10-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pathlib <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Path</span>
<span id="cb10-5"></span>
<span id="cb10-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Variables</span></span>
<span id="cb10-7">WebGISDR_filename <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"webgisdr"</span></span>
<span id="cb10-8">WebGISDR_dir <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Path(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"C:</span><span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">Program Files</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">\A</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">rcGIS</span><span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">Portal</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\t</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">ools</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">\w</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">ebgisdr"</span>)</span>
<span id="cb10-9">WebGISDR_backups_path <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Path(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\\</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">some-filepath-here"</span>)</span>
<span id="cb10-10">spacer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50</span></span></code></pre></div></div>
<p>Finally we’re at the primary three functions. Here’s <code>prune_copies()</code>.</p>
<ul>
<li>We create a list of paths from the directory indicated by the constant <code>WebGISDR_backups_path</code>, where the backup matches our user input (using dot notation from our argument names that we defined in the configure function). All backup copies look like: <code>yyyymmdd-hhmmss-TYPE.webgissite</code> with this in mind, we can run a wildcard (*) string search to grab anything that matches our <code>TYPE</code></li>
<li>Sort the list in descending order, this means that oldest copies are at the end</li>
<li>Create another list through a list comprehension so that we can have a pretty output that only includes file names</li>
<li>Create a list of backups to be retained by slicing the sorted list according to our user input</li>
<li>Create a list of backups to be pruned by slicing from the other direction</li>
<li>Iterate through our prune list and use <code>pathlib</code>’s <code>.unlink()</code> to remove those from the file system</li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> prune_copies(webgisdr_backups_path: Path, backup_arguments: argparse.Namespace):</span>
<span id="cb11-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Prunes a list of files according to the number of copies.</span></span>
<span id="cb11-3"></span>
<span id="cb11-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb11-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        webgisdr_backups_path (Path): a directory path to the .webgissite backups</span></span>
<span id="cb11-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        backup_arguments (argparse.Namespace): the user input arguments for backup and copies</span></span>
<span id="cb11-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb11-8">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Pruning backups"</span>)</span>
<span id="cb11-9">    WebGISDR_backups <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(</span>
<span id="cb11-10">        webgisdr_backups_path.glob(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"*</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>backup_arguments<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>mode<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">.webgissite"</span>)</span>
<span id="cb11-11">    )</span>
<span id="cb11-12">    WebGISDR_backups.sort(reverse<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb11-13">    file_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span>.name <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> WebGISDR_backups]</span>
<span id="cb11-14">    backups_retained <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> file_names[: backup_arguments.copies]</span>
<span id="cb11-15">    backups_pruned <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> file_names[backup_arguments.copies :]</span>
<span id="cb11-16">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> backups_pruned:</span>
<span id="cb11-17">        Path(webgisdr_backups_path, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span>).unlink()</span>
<span id="cb11-18">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\t</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">retained:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\t</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>backups_retained<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\t</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">pruned:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\t\t</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>backups_pruned<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb11-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span></span></code></pre></div></div>
<p>Next we’re setting the parameter of the <code>webgisdr.properties</code> file to match the user input type in the appropriately named <code>set_WebGISDR()</code> function.</p>
<ul>
<li>We create a <code>Path</code> to the file</li>
<li>Read in the file as a <code>string</code></li>
<li>Create a list of strings where each item is a new line</li>
<li>Iterate through the lines and find the line with the parameter <code>BACKUP_RESTORE_MODE</code>. Replace this list item with a string that utilizes our user input</li>
<li>Write the list of strings as string using a new line joiner</li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> set_WebGISDR(webgisdr_dir: Path, webgisdr_filename: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, backup_mode: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb12-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Sets the WebGISDR configuration file's BACKUP_RESTORE_MODE property</span></span>
<span id="cb12-3"></span>
<span id="cb12-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb12-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        webgisdr_dir (Path): the webgisdr tools directory path on the arcgisportal machine</span></span>
<span id="cb12-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        webgisdr_filename (str): the name of your webgisdr configuration file, it is normally 'webgisdr'</span></span>
<span id="cb12-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        backup_mode (str): the backup mode that was input as an argument</span></span>
<span id="cb12-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb12-9">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Setting </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>webgisdr_filename<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">.properties: BACKUP_RESTORE_MODE"</span>)</span>
<span id="cb12-10">    WebGISDR_properties <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Path(webgisdr_dir, <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>webgisdr_filename<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">.properties"</span>)</span>
<span id="cb12-11">    file_content <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> WebGISDR_properties.read_text()</span>
<span id="cb12-12">    lines <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> file_content.splitlines()</span>
<span id="cb12-13">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, line <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(lines):</span>
<span id="cb12-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"BACKUP_RESTORE_MODE ="</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> line:</span>
<span id="cb12-15">            <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\t</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">current value:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\t</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>line<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>split(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">' = '</span>)[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb12-16">            lines[i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"BACKUP_RESTORE_MODE = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>backup_mode<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb12-17">    WebGISDR_properties.write_text(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>.join(lines</span></code></pre></div></div>
<p>Lastly, we just need to run the standard WebGIS DR tool in the function <code>run_WebGISDR()</code>.</p>
<ul>
<li>Change directory to the WebGISDR tool within the Portal installation</li>
<li>Craft a string that will be used in the <code>subprocess.run()</code> function, this is analagous to opening a command prompt terminal</li>
<li>Run the <code>webgisdr</code> command</li>
</ul>
<p><em>Python code describing the <code>run_WebGISDR()</code> function:</em></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_WebGISDR(webgisdr_dir: Path, webgisdr_filename: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb13-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Runs the WebGISDR command-line utility</span></span>
<span id="cb13-3"></span>
<span id="cb13-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb13-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        webgisdr_dir (Path): the webgisdr tools directory path on the arcgisportal machine</span></span>
<span id="cb13-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        webgisdr_filename (str): the name of your webgisdr configuration file, it is normally 'webgisdr'</span></span>
<span id="cb13-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb13-8">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(</span>
<span id="cb13-9">        <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Changing Directory to </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>webgisdr_dir<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\t</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">running standard WebGISDR utility now</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>spacer<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>spacer<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb13-10">    )</span>
<span id="cb13-11">    os.chdir(webgisdr_dir)</span>
<span id="cb13-12">    cmd_webgisdr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"webgisdr --export --file </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>webgisdr_filename<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">.properties"</span></span>
<span id="cb13-13">    subprocess.run(cmd_webgisdr, shell<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb13-14">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>spacer<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>spacer<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">All Operations Complete</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>spacer<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>spacer<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb13-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span></span></code></pre></div></div>
<p>And that’s it. A fairly painless automation for setting and forgetting an out-of-the-box disaster recovery solution. When it runs it looks like this:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../img/posts/2024-12-18-cmd-snippet-02.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="terminal output when run"><img src="https://feralcatcolonist.github.io/img/posts/2024-12-18-cmd-snippet-02.png" class="img-fluid figure-img" alt="A terminal output from the running of the WebGISDR CLI wrapper"></a></p>
<figcaption>terminal output when run</figcaption>
</figure>
</div>
<p>If you’d like to use it in your own deployment you can find it on my GitHub here: <a href="https://github.com/FeralCatColonist/Spatial-Grimoire/blob/main/WebGISDR-cli-wrapper.py">https://github.com/FeralCatColonist/Spatial-Grimoire/blob/main/WebGISDR-cli-wrapper.py</a></p>


</section>

 ]]></description>
  <category>ArcGIS</category>
  <category>Enterprise</category>
  <category>Python</category>
  <guid>https://feralcatcolonist.github.io/posts/2024-12-18_command-line-ception/</guid>
  <pubDate>Wed, 18 Dec 2024 00:00:00 GMT</pubDate>
  <media:content url="https://feralcatcolonist.github.io/img/posts/2024-12-18-cli-ception.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Maybe the Real Treasure was the Deprecations We Made Along the Way?</title>
  <link>https://feralcatcolonist.github.io/posts/2024-11-07_the-deprecations-we-made-along-the-way/</link>
  <description><![CDATA[ 






<p>My toxic trait is that I am overly fond of novelty. In the technology field, this is something of a boon except for all the technical debt we leave in our wake. In the hashtag#Esri-verse, everyone loves talking about the impending sunset of ArcMap. Today, I wanted to take a moment to share another deprecation that affects the ArcGIS ecosystem. Namely, Map Viewer Classic. Some might say, best map viewer. RIP Map Viewer Classic.</p>
<p><a href="https://support.esri.com/en-us/knowledge-base/deprecation-map-viewer-classic-deprecation-000030662">https://support.esri.com/en-us/knowledge-base/deprecation-map-viewer-classic-deprecation-000030662</a></p>
<p>Anyway, in Q1 of 2026, these <em>classic</em> Web Map items will no longer be available in ArcGIS Online. So, the question for GIS professionals that administer an ArcGIS Online or ArcGIS Enterprise deployment, is, how do you find out which Web Map Viewers are <em>classic</em> vs which ones are the <em>new</em> Web Map Viewer?</p>
<p><a href="../../img/posts/2024-11-07-meme-that-should-be-something.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://feralcatcolonist.github.io/img/posts/2024-11-07-meme-that-should-be-something.png" class="img-fluid" alt="the four panel anakin and padme meme, where padme repeats herself in the second and fourth panels, the text now reads: panel1 Anakin: you need to convert all of your web map viewer classics to the new web map viewer; panel 2 Padme: that should be something we can filter against or see visually right?; panel 3 Anakin staring: no text; panel 4 Padme: that should be something we can filter against or see visually right?"></a></p>
<p>You could just, go and look at them right? Right?</p>
<p>You could, go in and open every Web Map and then make sure to re-save it in the <em>new</em> Web Map Viewer. Which, spoiler alert, you’ll (or preferably your users) need to do anyway. But you could be running into the overhead of accidentally saving items that are already <em>new</em> Web Map Viewer.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../img/posts/2024-11-07-ago.snippet-01.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="How would we even know the difference looking at it like this?"><img src="https://feralcatcolonist.github.io/img/posts/2024-11-07-ago.snippet-01.png" class="img-fluid figure-img" alt="a screen shot within ArcGIS Online showing three map viewers with different underlying properties. they all look the same"></a></p>
<figcaption>How would we even know the difference looking at it like this?</figcaption>
</figure>
</div>
<p>But, what if? You had a map, of sorts, to show you which items needed to be fixed?</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../img/posts/2024-11-07-dataframe-snippet.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="If you know where to look, it’s easy to see the difference."><img src="https://feralcatcolonist.github.io/img/posts/2024-11-07-dataframe-snippet.png" class="img-fluid figure-img" alt="Dataframe showing that Web Map items are different depending on how they were created, with Web Map Viewer new or classic."></a></p>
<figcaption>If you know where to look, it’s easy to see the difference.</figcaption>
</figure>
</div>
<p>Behold. Some code that does just that. Assuming you are logged into ArcGIS Pro with the account based on the organization you’re looking to audit, then running this is fairly straight forward. We can still walk through the code to de-mystify it a little.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> arcgis</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb1-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> time</span>
<span id="cb1-4"></span>
<span id="cb1-5">gis <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> arcgis.gis.GIS(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pro"</span>)</span>
<span id="cb1-6">content_list <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gis.content.search(query<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"*"</span>, item_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Web Map"</span>, max_items<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb1-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Org-wide there are </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(content_list)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> Web Map items"</span>)</span>
<span id="cb1-8">map_viewer_classic_items <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb1-9">i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb1-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> item <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> content_list:</span>
<span id="cb1-11">    time.sleep(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb1-12">    item_data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> item.get_data()</span>
<span id="cb1-13">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"authoringApp"</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> item_data:</span>
<span id="cb1-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span></span>
<span id="cb1-15">    authoringApp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> item_data[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"authoringApp"</span>]</span>
<span id="cb1-16">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> authoringApp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"WebMapViewer"</span>:</span>
<span id="cb1-17">        i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-18">        map_viewer_classic_items[i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb1-19">            item.itemid,</span>
<span id="cb1-20">            authoringApp,</span>
<span id="cb1-21">            item.numViews,</span>
<span id="cb1-22">            item.owner,</span>
<span id="cb1-23">            item.title,</span>
<span id="cb1-24">        ]</span>
<span id="cb1-25">df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.DataFrame.from_dict(</span>
<span id="cb1-26">    map_viewer_classic_items,</span>
<span id="cb1-27">    orient<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"index"</span>,</span>
<span id="cb1-28">    columns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"itemid"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"authoringApp"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"views"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"owner"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"title"</span>],</span>
<span id="cb1-29">)</span>
<span id="cb1-30">df.to_csv(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"C:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">\s</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">ome-folder-path</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">\W</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">ebMapViewer-manifest</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">.</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">csv"</span>, index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb1-31"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\t</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">recorded a total of </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> Map Viewer Classic items"</span>)</span></code></pre></div></div>
<p>Okay, so the first few lines are importing the libraries we need, fairly standard stuff. We use arcgis for ArcGIS ecosystem things, we’ll use pandas to ultimately shuttle the data into a spreadsheet, and lastly, we use the time library to rate-limit our API calls against our ArcGIS deployment.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> arcgis</span>
<span id="cb2-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb2-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> time</span></code></pre></div></div>
<p>Then we’ll log into our ArcGIS deployment (Online or Enterprise) based on whatever organization we’re currently logged in to. There are other authentication methods that I won’t discuss that can be found here:</p>
<p><a href="https://developers.arcgis.com/python/latest/guide/working-with-different-authentication-schemes/">https://developers.arcgis.com/python/latest/guide/working-with-different-authentication-schemes/</a></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">gis <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> arcgis.gis.GIS(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pro"</span>)</span></code></pre></div></div>
<p>Next we’ll search through all of our organization’s ArcGIS items, with a filter based on, is it a Web Map? Unfortunately, that by itself doesn’t get us where we need to go. Because I like to pretend we’re in the Matrix, I tend to use print statements to let me know where we’re at in the program. This f-string lets us know the number of returned items that matched our search for Web Maps in the organization.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">content_list <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gis.content.search(query<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"*"</span>, item_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Web Map"</span>, max_items<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb4-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Org-wide there are </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(content_list)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> Web Map items"</span>)</span></code></pre></div></div>
<p>This next part looks like a lot, but it isn’t so bad.</p>
<p>We make an empty dictionary called <code>map_viewer_classic_items</code> and an integer called <code>i</code>, we’ll use these to create our data structure. For every item returned by the <code>content_manager</code> in the <code>content_list</code> variable, we’ll perform the following actions:</p>
<ol type="1">
<li><code>time.sleep(1)</code> | sleep for one second, this makes sure that we avoid hitting our ArcGIS Deployment too frequently and returning a 404 error</li>
<li><code>item_data = item.get_data()</code> | this is the <code>arcgis</code> library, it is a call that gets the JSON data backing an item, it is returned as a dictionary that we’ll call item_data</li>
<li><code>if "authoringApp" not in item_data:</code> | check if the key <code>authoringApp</code> is in the dictionary <code>item_data</code>, this is important because not every kind of Web Map has that <code>authoringApp</code> key, and assuming it is there can cause a <code>KeyError</code> exception. If the current item doesn’t have that parameter, we continue the for loop to the next iteration</li>
<li><code>authoringApp = item_data["authoringApp"]</code> | this is an intermediate variable, we’re grabbing the value associated with the <code>authoringApp</code> key for layer use in the loop</li>
<li><code>if authoringApp == "WebMapViewer":</code> | we check to see if the value of the <code>authoringApp</code> key is <code>WebMapViewer</code>. The new Web Map Viewer’s value is <code>ArcGISMapViewer</code>. So if it is a <em>classic</em> Map Viewer, we’ll increment our <code>i</code> by <code>1</code>. We use this value to create new entries in our dictionary</li>
<li><code>map_viewer_classic_items[i] =</code> | here we write a new dictionary entry per each <em>classic</em> Web Map Viewer that we found, and populate it with values from the <code>item_data</code> dictionary along with the value of the <code>authoringApp</code> key– –this is a little redundant but I originally was looking for any type of Web Map and was filtering the results in a spreadsheet later.</li>
</ol>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">map_viewer_classic_items <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb5-2">i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb5-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> item <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> content_list:</span>
<span id="cb5-4">    time.sleep(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb5-5">    item_data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> item.get_data()</span>
<span id="cb5-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"authoringApp"</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> item_data:</span>
<span id="cb5-7">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span></span>
<span id="cb5-8">    authoringApp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> item_data[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"authoringApp"</span>]</span>
<span id="cb5-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> authoringApp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"WebMapViewer"</span>:</span>
<span id="cb5-10">        i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb5-11">        map_viewer_classic_items[i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb5-12">            item.itemid,</span>
<span id="cb5-13">            authoringApp,</span>
<span id="cb5-14">            item.numViews,</span>
<span id="cb5-15">            item.owner,</span>
<span id="cb5-16">            item.title,</span>
<span id="cb5-17">        ]</span></code></pre></div></div>
<p>And we’re past the hard part! Kind of. This next thing does a lot. We use a standard pandas function <code>from_dict()</code> to create a dataframe from a dictionary. We’ll use the <code>map_viewer_classic_items</code> dictionary that we populated in the for loop above, then we’ll use the index orientation, which means that each dictionary record will correspond to a record row in the dataframe, then we’ll give a list of names for the columns that correspond to the item’s data attributes.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.DataFrame.from_dict(</span>
<span id="cb6-2">    map_viewer_classic_items,</span>
<span id="cb6-3">    orient<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"index"</span>,</span>
<span id="cb6-4">    columns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"itemid"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"authoringApp"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"views"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"owner"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"title"</span>],</span>
<span id="cb6-5">)</span></code></pre></div></div>
<p>Whew. And we’re really doing this because we want to push it down to a spreadsheet using an easy button, <code>to_csv()</code> is a panda’s function that writes a dataframe to a CSV. Then we’ll print another f-string function which includes a count <code>i</code> of the total number of items written to the .csv that we exported. And you could just open the <code>.csv</code> to find out but. Matrix.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">df.to_csv(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"C:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">\s</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">ome-folder-path</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">\W</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">ebMapViewer-manifest</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">.</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">csv"</span>, index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb7-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\t</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">recorded a total of </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> Map Viewer Classic items"</span>)</span></code></pre></div></div>
<p>Anyway. That’s the thing. You have a year or so to go out and fix it.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../img/posts/2024-11-07-python-code-sample.png" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="We’re going to need a bigger [GIS Administrator]"><img src="https://feralcatcolonist.github.io/img/posts/2024-11-07-python-code-sample.png" class="img-fluid figure-img" alt="a screen shot of the entire python script as seen from a jupyter notebook within VSCode"></a></p>
<figcaption>We’re going to need a bigger [GIS Administrator]</figcaption>
</figure>
</div>



 ]]></description>
  <category>AGOL</category>
  <category>ArcGIS</category>
  <category>Python</category>
  <guid>https://feralcatcolonist.github.io/posts/2024-11-07_the-deprecations-we-made-along-the-way/</guid>
  <pubDate>Sun, 17 Nov 2024 00:00:00 GMT</pubDate>
  <media:content url="https://feralcatcolonist.github.io/img/posts/2024-11-07-meme-all-map-viewer-items.png" medium="image" type="image/png" height="81" width="144"/>
</item>
<item>
  <title>Another ArcPy PSA (ArcGIS Pro 3.3)</title>
  <link>https://feralcatcolonist.github.io/posts/2024-06-14_psa-another-arcpy-psa/</link>
  <description><![CDATA[ 






<p>Just a small #ArcPy public service announcement for anyone creating a standalone environment via #conda using #ArcGISPro 3.3. The currently hosted arcpy package (<code>py311_arcgispro_52583</code>) may not work on your machine utilizing the normal conda command like:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode cmd code-with-copy"><code class="sourceCode dosbat"><span id="cb1-1">conda install esri::arcpy</span></code></pre></div></div>
<p>You’ll be alerted if this is an issue because the packages will refuse to install, instead there will be a series of cascading errors each ending with something like:</p>
<pre><code>Runtime Error: OpenlSSL 3.0's legacy provider failed to load. This is a fatal error by default, but cryptography supports running without legacy algorithms by .</code></pre>
<p>Luckily this isn’t a deal breaker, you’ll just need to specify the install order with the following commands, this assumes you are starting in (<code>base</code>) conda:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode cmd code-with-copy"><code class="sourceCode dosbat"><span id="cb3-1">conda create <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-n</span> the<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-name-of-your-new-env</span></span>
<span id="cb3-2">conda activate the<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-name-of-your-new-env</span></span>
<span id="cb3-3">conda install esri::python</span>
<span id="cb3-4">conda install esri::cryptography</span>
<span id="cb3-5">conda install esri:arcpy</span></code></pre></div></div>
<p>There ya go. Hopefully you find this and it is relatively painless. Or it gets patched and somehow you stumbled upon this and it seems a bit esoteric.</p>
<p><a href="../../img/posts/2024-06-14-update-or-die.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://feralcatcolonist.github.io/img/posts/2024-06-14-update-or-die.png" class="img-fluid" alt="an image of the join or die segmented snake, the words have been altered to say &quot;update or die"></a></p>



 ]]></description>
  <category>Conda</category>
  <category>ArcGIS</category>
  <guid>https://feralcatcolonist.github.io/posts/2024-06-14_psa-another-arcpy-psa/</guid>
  <pubDate>Fri, 14 Jun 2024 00:00:00 GMT</pubDate>
  <media:content url="https://feralcatcolonist.github.io/img/posts/2024-06-14-update-or-die.png" medium="image" type="image/png" height="105" width="144"/>
</item>
<item>
  <title>PSA: Your ArcPy Might be Broken</title>
  <link>https://feralcatcolonist.github.io/posts/2023-11-08_psa-your-arcpy-might-be-broken/</link>
  <description><![CDATA[ 






<section id="the-less-sensational-title-might-be" class="level2">
<h2 class="anchored" data-anchor-id="the-less-sensational-title-might-be">The less sensational title might be:</h2>
<section id="arcpy-minor-upgrades-are-a-minor-headache." class="level3">
<h3 class="anchored" data-anchor-id="arcpy-minor-upgrades-are-a-minor-headache."><em>ArcPy Minor Upgrades are a Minor Headache.</em></h3>
<p>Two years ago I wrote <a href="../../2021-03-19-standalone-arcgis-conda.html">a long article</a> on how to create a standalone ArcPy environment; normally, I’d say it is worth a read but its probably out of date. I’ve seen a few more articles since then covering the same subject matter so I’m hoping that this a more common practice. Why am I writing this? Yesterday, Esri released a new minor update to ArcGIS Pro from <code>3.1.x</code> to <code>3.2.0</code> and judging from the <strong>What’s New</strong> page this is one of the more feature-rich releases to the software since <code>2.7.x</code>.</p>
<p><a href="../../img/posts/2023-11-08-doc-snippet-esri-01.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://feralcatcolonist.github.io/img/posts/2023-11-08-doc-snippet-esri-01.png" class="img-fluid" alt="a screenshot of the top of the documentation linked below"></a></p>
<p>https://pro.arcgis.com/en/pro-app/latest/get-started/whats-new-in-arcgis-pro.htm</p>
<p>With a new minor release comes new binaries, meaning that any Conda environments created within the ArcGIS Pro GUI or ArcPy installations created in a standalone fashion need to be freshly installed. You’ll know if this happens when you see an error like this:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../img/posts/2023-11-08-cmd-snippet-01.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="A Conda error showing that an ArcPy version tied to ArcGIS Pro 3.1.x will not work with an ArcGIS Pro 3.2.x installation."><img src="https://feralcatcolonist.github.io/img/posts/2023-11-08-cmd-snippet-01.png" class="img-fluid figure-img" alt="A Conda error showing that an ArcPy version tied to ArcGIS Pro 3.1.x will not work with an ArcGIS Pro 3.2.x installation."></a></p>
<figcaption>A Conda error showing that an ArcPy version tied to ArcGIS Pro 3.1.x will not work with an ArcGIS Pro 3.2.x installation.</figcaption>
</figure>
</div>
<p>Normally one could just run a command like:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode cmd code-with-copy"><code class="sourceCode dosbat"><span id="cb1-1">conda update some<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-library</span></span></code></pre></div></div>
<p>Because of the binary situation above, this does not work with the ArcPy library. We have two options for removing and re-installing ArcPy which I’ll cover. Esri has a convenience feature for “upgrading” your ArcPy environment; however, the actual mechanism works like this:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../img/posts/2023-11-08-doc-snippet-esri-02.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="This update process deletes the environment… …and re-creates it"><img src="https://feralcatcolonist.github.io/img/posts/2023-11-08-doc-snippet-esri-02.png" class="img-fluid figure-img" alt="a screenshot of the esri documentation outlining the upgrade process"></a></p>
<figcaption>This update process deletes the environment… …and re-creates it</figcaption>
</figure>
</div>
<p>If you are using ArcPy in a standalone kernel for Python, and you have, or are planning to, install the new minor release update (3.2) to ArcGIS Pro you will need to reinstall ArcPy in your Conda environment. Let’s look at a few options that you have for performing the reinstall; we’ll imagine a few different scenarios.</p>
</section>
</section>
<section id="scenario-0---you-can-do-a-standalone-installation-of-arcpy" class="level2">
<h2 class="anchored" data-anchor-id="scenario-0---you-can-do-a-standalone-installation-of-arcpy">Scenario 0 - You Can do a Standalone Installation of ArcPy!?</h2>
<p>Yes. It’s super easy and this is how I do it! From an Anaconda prompt window, we’ll do the following using the placeholder ENV_NAME for the name of your standalone Conda environment. Feel free to name it whatever you like.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode cmd code-with-copy"><code class="sourceCode dosbat"><span id="cb2-1">conda create <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-n</span> ENV_NAME</span>
<span id="cb2-2">conda activate ENV_NAME</span>
<span id="cb2-3">conda install <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-c</span> esri arcpy</span></code></pre></div></div>
<p>The first line creates the environment, you’ll need to press the “y” key and then the “enter” key to complete the installation. The second line will activate this new environment. The third line installs ArcPy and all of its related packages into it, this also includes the ArcGIS API for Python.</p>
<p><a href="https://anaconda.org/esri/arcpy">https://anaconda.org/esri/arcpy</a></p>
</section>
<section id="scenario-1---a-simple-scenario" class="level2">
<h2 class="anchored" data-anchor-id="scenario-1---a-simple-scenario">Scenario 1 - A Simple Scenario</h2>
<p>I just installed ArcPy and nothing else. Congratulations! You can remove and re-install ArcPy without thinking about it too hard.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode cmd code-with-copy"><code class="sourceCode dosbat"><span id="cb3-1">conda deactivate ENV_NAME</span>
<span id="cb3-2">conda remove <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-n</span> ENV_NAME -<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-all</span></span>
<span id="cb3-3">conda install <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-c</span> esri arcpy</span></code></pre></div></div>
<p>The first line deactivates your environment if it is open already. The second line removes all packages. The last line will reinstall ArcPy. That’s it, your environment will continue to work as expected and won’t break any of your scheduled tasks if you had them written against the python.exe path within your kernel.</p>
</section>
<section id="scenario-2---i-uh-installed-a-few-other-packages" class="level2">
<h2 class="anchored" data-anchor-id="scenario-2---i-uh-installed-a-few-other-packages">Scenario 2 - I, uh, Installed a Few Other Packages</h2>
<p>This doesn’t surprise me. Anaconda is a package manager. You use it to manage packages. You like to dabble in GeoPandas and other libraries of substance. Me too. If you know exactly what you’re looking for, you can trawl through a .yaml file:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode cmd code-with-copy"><code class="sourceCode dosbat"><span id="cb4-1">conda activate ENV_NAME</span>
<span id="cb4-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"some path where you want the exported file"</span></span>
<span id="cb4-3">conda env export <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&gt;</span> some_file_name.yml</span></code></pre></div></div>
<p>The ArcPy library has a lot of dependencies so browsing it like this isn’t really my cup of tea. Although, a .yaml is a great way to share your environment with someone else. I think an easier way to look at your package installations is to look at your revision history. This is Anaconda’s version control, when you access it, the output is a numbered list of the libraries and their versions installed at each command:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode cmd code-with-copy"><code class="sourceCode dosbat"><span id="cb5-1">conda activate ENV_NAME</span>
<span id="cb5-2">conda list -<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-revisions</span></span></code></pre></div></div>
<p>This batched version of downloads should help you to figure out which libraries you downloaded (and in what order) so you have a better idea of how you need to re-create your environment. ArcPy is normally the first install I make after creating the environment, so I rolled back my environment and then re-installed ArcPy like this:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode cmd code-with-copy"><code class="sourceCode dosbat"><span id="cb6-1">conda install -<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-revision</span> 0</span>
<span id="cb6-2">conda install <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-c</span> esri arcpy</span></code></pre></div></div>
<p>Your syntax will likely be different depending on the number if libraries you need to include and in what order of installation those happened.</p>
<p>And it’s kind of that easy. Admittedly, not as easy as upgrading a library with the update command but such is the life of a licensed software library.</p>
<p><a href="../../img/posts/2023-11-08-logo-anaconda-esri.png" class="lightbox" data-gallery="quarto-lightbox-gallery-4"><img src="https://feralcatcolonist.github.io/img/posts/2023-11-08-logo-anaconda-esri.png" class="img-fluid" alt="a screenshot of the top of the documentation linked below"></a></p>


</section>

 ]]></description>
  <category>Conda</category>
  <category>ArcGIS</category>
  <guid>https://feralcatcolonist.github.io/posts/2023-11-08_psa-your-arcpy-might-be-broken/</guid>
  <pubDate>Wed, 08 Nov 2023 00:00:00 GMT</pubDate>
  <media:content url="https://feralcatcolonist.github.io/img/posts/2023-11-08-logo-anaconda-esri.png" medium="image" type="image/png" height="48" width="144"/>
</item>
<item>
  <title>I Don’t Know the Difference Between CONTAINS and WITHIN</title>
  <link>https://feralcatcolonist.github.io/posts/2022-09-15_contains-and-within/</link>
  <description><![CDATA[ 






<p>As much as LinkedIn keeps pestering me to, I’m not an influencer type. I enjoy puzzles and when I solve something that has challenged me, my first instinct is to share those insights with others. A pitfall with that process is sharing things that are sometimes inaccurate or just plain wrong. I recently had a situation like that, so here’s a correction!</p>
<p><a href="../../img/posts/2022-09-15-linkedin-impressions.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://feralcatcolonist.github.io/img/posts/2022-09-15-linkedin-impressions.png" class="img-fluid" alt="a screenshot, impressions of your post 11,768"></a></p>
<p>This was probably the most popular thing I’ve ever posted. It’s a snippet of the #ArcGIS expression language Arcade. The code is <em>supposed</em> to return the attributes of an n-number of features within a container. It didn’t do that; for a few reasons.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> features_to_count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">FEATURESETBYNAME</span>($datastore<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Parcels_0sqft"</span></span>
<span id="cb1-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> features_within <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">WITHIN</span>($feature<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> features_to_count)</span>
<span id="cb1-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> calculated_area <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb1-4"></span>
<span id="cb1-5"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">COUNT</span>(features_within) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) {</span>
<span id="cb1-6">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span>(<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> within_feature <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> features_within) {</span>
<span id="cb1-7">    calculated_area <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> calculated_area <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> within_feature<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">Shape_Area</span></span>
<span id="cb1-8">  }</span>
<span id="cb1-9">}</span>
<span id="cb1-10"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">CONSOLE</span>(calculated_area)</span>
<span id="cb1-11"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> calculated_area)</span></code></pre></div></div>
<ol type="1">
<li>the features_to_count variable was actually the container <code>Parcels_0sqft</code> and not the features within it. When it returned results, it was the area of the container itself.</li>
<li>switching the <code>feature_to_count</code> variable to the features within the container “<code>BuildingFootprints</code> returned nothing. This is because that parameter only accepts a <code>Feature/Geometry</code> and not a <code>FeatureSet</code>.</li>
<li>I provided a clarification to the originally posted code not working, amended the code to use <code>INTERSECTS()</code> instead of <code>WITHIN()</code>, and suggested that there was a bug in the function <code>WITHIN()</code>. That was wrong.</li>
</ol>
<p>And this brings us to the title of this piece:</p>
<section id="i-dont-know-the-difference-between-contains-and-within" class="level1">
<h1>I Don’t Know the Difference Between CONTAINS and WITHIN</h1>
<p><a href="../../img/posts/2022-09-15-the-meme.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2"><img src="https://feralcatcolonist.github.io/img/posts/2022-09-15-the-meme.png" class="img-fluid" alt="The office meme, coporate needs you to find the difference...altered as follows: Esri needs you to find the difference between this picture and this picture; the caption on each picture correpsonds to WITHIN and CONTAINS; the bottom panel shows the author's GitHub avatar superimposed on Pam; they're the same picture"></a></p>
<p>Fortunately, that’s not true anymore. However, I did go through a support ticket process with #esri to finally come to that realization. To be fair, the differences are laid out in the #arcgisarcade documentation (a bit better in <code>CONTAINS</code> than <code>WITHIN</code>). The short version is like this:</p>
<ul>
<li><p><code>CONTAINS</code>, is there something inside of the feature (a container)?</p></li>
<li><p><code>WITHIN</code>, is the feature inside of something (a container)?</p></li>
</ul>
<p>A good idiom here would be, two sides of the same coin. Similar operations that differ in their directionality.</p>
<p><code>CONTAINS()</code> documentation:</p>
<p><a href="../../img/posts/2022-09-15-doc-snippet-contains.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3"><img src="https://feralcatcolonist.github.io/img/posts/2022-09-15-doc-snippet-contains.png" class="img-fluid" alt="An image of the documentation for CONTAINS() with highlighting to bring attention to the first sentence in the description."></a></p>
<p><code>WITHIN()</code> documentation:</p>
<p><a href="../../img/posts/2022-09-15-doc-snippet-within.png" class="lightbox" data-gallery="quarto-lightbox-gallery-4"><img src="https://feralcatcolonist.github.io/img/posts/2022-09-15-doc-snippet-within.png" class="img-fluid" alt="An image of the documentation for WITHIN() with highlighting to bring attention to the first sentence in the description."></a></p>
<p>A simple tweak to the diagrams provided could make them more explicit:</p>
<p><a href="../../img/posts/2022-09-15-doc-snippet-contains-edit.png" class="lightbox" data-gallery="quarto-lightbox-gallery-5"><img src="https://feralcatcolonist.github.io/img/posts/2022-09-15-doc-snippet-contains-edit.png" class="img-fluid" alt="a diagram of the CONTAINS() function with an arrow pointing to what will be output as a result"></a></p>
<p><a href="../../img/posts/2022-09-15-doc-snippet-within-edit.png" class="lightbox" data-gallery="quarto-lightbox-gallery-6"><img src="https://feralcatcolonist.github.io/img/posts/2022-09-15-doc-snippet-within-edit.png" class="img-fluid" alt="a diagram of the WITHIN() function with an arrow pointing to what will be output as a result"></a></p>
<p>And if you’ve stuck with me this far, here’s the pay-off; a second revision showing how that original code should have been written, with variable names that can be linked back to the documentation:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//https://developers.arcgis.com/arcade/function-reference/geometry_functions/#contains2</span></span>
<span id="cb2-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> insideGeometry <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">FEATURESETBYNAME</span>($datastore<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"BuildingFootprints"</span>)</span>
<span id="cb2-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> containerGeometry <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> $feature</span>
<span id="cb2-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> features_within_container <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">CONTAINS</span>(containerGeometry<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> insideGeometry)</span>
<span id="cb2-5"></span>
<span id="cb2-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">var</span> calculated_area <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">DefaultValue</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Sum</span>(features_within_container<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Shape_Area"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb2-7"></span>
<span id="cb2-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">CONSOLE</span>(calculated_area)</span>
<span id="cb2-9"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> calculated_area</span></code></pre></div></div>


</section>

 ]]></description>
  <category>Arcade</category>
  <category>GIS</category>
  <guid>https://feralcatcolonist.github.io/posts/2022-09-15_contains-and-within/</guid>
  <pubDate>Thu, 15 Sep 2022 00:00:00 GMT</pubDate>
  <media:content url="https://feralcatcolonist.github.io/img/posts/2022-09-15-the-meme.png" medium="image" type="image/png" height="81" width="144"/>
</item>
<item>
  <title>The Bus Factor</title>
  <link>https://feralcatcolonist.github.io/posts/2021-06-16_the-bus-factor/</link>
  <description><![CDATA[ 






<p><em>This article was originally published in URISA’s The GIS Professional Issue 301</em></p>
<p>The Bus Factor is a darkly humorous turn of phrase describing key person risk that arose from software development and the consequence of losing project members that possess critical skills/knowledge. Put in cruder terms: if you were hit by a bus tomorrow, who could do your job, or even know what your job was? While the metaphor is grim, it can accurately describe the finality of losing a team member with whom you had no contact with outside of work. The results of such a separation can range from mildly inconvenient to absolutely devastating in terms of systems going offline, projects stalling out, irreparable breaks, and deliverables undelivered. We’ll discuss strategies to mitigate these risks across the spectrum of organizational sizes along with instructions on using widely-available technologies.</p>
<section id="the-loneliest-number" class="level2">
<h2 class="anchored" data-anchor-id="the-loneliest-number">The Loneliest Number</h2>
<p>The term is meant to quantify the number of project members that would need to be absent to create chaos. A factor of 1 would be the riskiest model, with each increase providing substantially more protection to your organization. However, with most GIS shops being a one-person operation, it may seem that your Bus Factor could never be greater than 1. At this basic level, there will almost always be a disruption to the flow of work. Despite a quick turnaround in hiring, the interruptions could span months. It doesn’t take much to imagine the horrors of crawling through an unknown network, digging into the husk of a former employee’s Outlook data file for project archives, or being emailed a pdf map to replicate and then searching through mounds of “Export_Output”-style shapefiles and map templates with names like “final2_maprequest_new”.</p>
<p>At this size of organization, one of the best outcomes that we can hope for is mitigation through documentation. While not the most exciting of topics, project documentation is vital and plenty has been written about it by people far more knowledgeable than me. I’d like to hit on some of the highlights and ethos of where to spend your energy in the realm of documentation.</p>
<p>For digital work, data is both nonrival and has near zero marginal cost to replicate; meaning, the largest cost to an organization is the development of the deliverable, afterwards it can be used and maintained with minimal upkeep. As such, keeping track of template files is incredibly important– –this is relatively easy with an MXD or APRX but requires additional effort with documenting commercial off-the-shelf (COTS) products that may have a large degree of customization (I’m looking at you ArcGIS Online). With template files, one of the greatest dangers is not updating source destinations after a major infrastructure upgrade; think about the last time your GIS database had a name change or when you migrated networks?</p>
<p>The other foundational rule of documentation is one of the most overlooked areas of data governance generally, metadata. Your data needs a dictionary. It needs an intent. And if you aren’t aware of the data provenance then how will it be authenticated, updated, or re-created? If you would like a slightly terrifying exercise, try running this meta-data dump</p>
<p><code>gist.github.com/FeralCatColonist/7504ab0c380542f12220fe62142082c1</code></p>
<p>on your GIS repository and emailing it to your supervisor. Again, the goal of a one-person shop is not to prevent disruption but to ask yourself, if a competent replacement was hired, would they be able to step into this role with minimal effort?</p>
<p><a href="../../img/posts/2021-06-16-drawn-trolley-walking-01.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://feralcatcolonist.github.io/img/posts/2021-06-16-drawn-trolley-walking-01.jpg" class="img-fluid" alt="A photograph with a pencil-sketch filter showing a person walking in front of a trolley car"></a></p>
</section>
<section id="a-broader-view-of-indispensable" class="level2">
<h2 class="anchored" data-anchor-id="a-broader-view-of-indispensable">A Broader View of Indispensable</h2>
<p>Who on your team represents a single point of failure or bottleneck? As a thought experiment, go through each member of your team and enumerate their projects and skillset. If you’re having trouble with either of those, or their skillset is unique, you’ve found a Bus Factor of 1. This can be repeated for known deliverables or other statutory responsibilities. The management types among us practicing a “need-to-know” style represent a Bus Factor of 1 as well. While Saturday morning cartoons taught me that knowing is half the battle, the more exciting part of this story is what to do next.</p>
<p>Ideally, your Bus Factor should be as large as your team. If this strikes you as a hot take and you want to talk to me about the merits of specialization or titled roles, feel free; I’ll respond with this: you should be investing in your employees. Full stop. Aside from that, there are definite benefits for having a 360-degree view of your unit and how it interacts with the rest of your organization. This doesn’t mean that the efficacy of everyone’s skillset needs to be the same, but everyone should be familiar, and able to interact, with a workflow if the need arises.</p>
<p>There are many ways to increase the Bus Factor for common deliverables: lunch and learns, side-by-sides or shadowing, and how-to videos. The lunch and learn serves multiple purposes, it works as a team building activity, practice for presentations, and familiarizes staff members with a workflow; in the Before Time, this could be a fairly fun event with catered food from a local restaurant. In the Pandemic Time, this becomes a remote affair with lunch brought to you by your pantry. In the New Normal, a side-by-side or shadowing becomes a little less cramped as individuals don’t need to physically huddle around a desk and everyone can have a front seat using a screen sharing technology. How-to videos are great and we’ll delve into that in the companion video! Spoiler alert, it’s not complicated and the tools to do it are free.</p>
<p>In terms of programming and knowledge work, increasing the Bus Factor takes a bit more effort or rather, a dedicated framework. My recommendation here is to adopt an agile methodology that makes use of the following artifacts: personal Kanban boards, code reviews, and version control. A publicly visible Kanban prioritizes what’s important and allows the collaborative and inquisitive team to cross-train on new projects that enter the workspace. While a common practice in software engineering, code review is unheard of in most spatial departments. For the uninitiated, code review is a public assessment of programming scripts in terms of style, patterns, and overall usability. It’s an excellent way to inform team members about the mechanics of an automation and to make sure that coding is consistent and legible amongst peers. This goes to the heart of the Bus Factor: your scripts are useless if the only person who knows how it works is you. Finally, version control attempts to cushion your code base against significant errors by providing provenance; when an item changed and why, and the ability to rollback your code to a point where it actually worked. You should be using Git. It’s easy to learn and there are a variety of platforms to choose from. Consider also opening your source code (through platforms like GitHub or GitLab). It’s a way to collaborate across organizations and to benefit beyond the numbers/talent of your immediate team.</p>
</section>
<section id="an-ounce-of-prevention" class="level1">
<h1>An Ounce of Prevention</h1>
<p><a href="../../img/posts/2021-06-16-drawn-trolley-walking-02.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-2"><img src="https://feralcatcolonist.github.io/img/posts/2021-06-16-drawn-trolley-walking-02.jpg" class="img-fluid" alt="Another photograph with a pencil-sketch filter showing a person walking in front of a trolley car"></a></p>
<p>The axiom attributed to Benjamin Franklin places a 16:1 ratio on preventing a disaster as opposed to the cost of responding to it; in a world of mega-fires, pandemic, and Snowmageddons that ratio seems somewhat provincial. In terms of our own organizations, the disruption of any instantaneously available deliverable incurs an astronomical cost above what was effectively zero. As horrifying as these worst-case scenarios may be, people are pretty great at being complacent. From a biological perspective, it probably helps us to live longer if we’re not in a constant state of anxiety about the future. Our underlying motivations for increasing our Bus Factor do not need to be rooted in these sorts of negative emotions, and can instead be undertaken in a spirit of commitment to transparency and life-long learning.</p>
<p>One of the great tragedies of the digital revolution is the abuse of the principle of least privilege, restricting access to data and programs unless <em>ordained</em> as necessary for a certain user or workflow. Aside from over-securing data and inhibiting innovation, this process tends to bleed well beyond the system architecture and deprives an organization of its greatest resources: access to human capital. Transparency is both prevention and cure for this state of affairs. Obviously, building a culture of transparency is harder than maintaining one. Some ideas to increase transparency are daily standups, personal Kanban boards, weekly one-on-ones, and open-door policies for executive-level meetings. The undercurrent here is a level respect for employees at all levels of the organization and the idea that contributions can come from anywhere. These contributions don’t happen in a vacuum, work must be visible!</p>
<p>During this Second Machine Age, some of the defining hallmarks are velocity, volume, and variety of information. If there was ever a time when a person could satisfy themselves with never learning anything new, that time has most certainly passed. Aside from the benefits of personal actualization, our current environment demands that we constantly learn to maintain our position within it. At your organization learning should be encouraged, subsidized, and celebrated. The absolute worst outcome here is failing to invest in your employees; a self-inflicted anemia, starving them of new knowledge and skills. In the pandemic, we have been inundated with an enormous amount of remote learning opportunities that were previously restricted to paid, in-person offerings; a future struggle will be to maintain our access to these resources when the masks come off.</p>
<p>With the end in mind, both figuratively and literally, the Bus Factor is less klaxon and more health gauge for your organization. While some areas of your organization may be quite technically advanced, your overall maturity is low if those capabilities are based on personality and not institutionally defined. The paths to increase your Bus Factor follow many best practices of winning organizational cultures: personal growth, collaboration, and effective communication. In that sense, the Bus Factor is less about the destination and more the journey. Taking the time to document your work, working well with others, and learning new things, saves time in the long-term and fosters a more attractive workplace overall.</p>
<p><a href="../../img/posts/2021-06-16-drawn-trolley-walking.gif" class="lightbox" data-gallery="quarto-lightbox-gallery-3"><img src="https://feralcatcolonist.github.io/img/posts/2021-06-16-drawn-trolley-walking.gif" class="img-fluid" alt="An animated gif of several photographs with a pencil-sketch filter showing a person walking in front of a trolley car"></a></p>
<p><em>Companion Video Demonstrating Common Video Documentation Workflows</em> <a href="https://www.youtube.com/watch?v=S-biFgYKdNY">https://www.youtube.com/watch?v=S-biFgYKdNY</a></p>


</section>

 ]]></description>
  <category>GIS</category>
  <category>Python</category>
  <category>Metadata</category>
  <category>Career</category>
  <guid>https://feralcatcolonist.github.io/posts/2021-06-16_the-bus-factor/</guid>
  <pubDate>Wed, 16 Jun 2021 00:00:00 GMT</pubDate>
  <media:content url="https://feralcatcolonist.github.io/img/posts/2021-06-16-the-gis-professional-cover.png" medium="image" type="image/png" height="41" width="144"/>
</item>
<item>
  <title>Building a Standalone ArcGIS Scripting Environment in Anaconda</title>
  <link>https://feralcatcolonist.github.io/posts/2021-03-19_standalone-arcgis-conda/</link>
  <description><![CDATA[ 






<p>The newest release of ArcGIS Pro 2.7 allows users to download the Python library ArcPy, previously only available from within the installation folder. This means the two major ArcGIS Python libraries ArcPy and the ArcGIS API for Python are finally available to use in a code-only environment. Previously, using both of these libraries in a code-only environment was a bit convoluted, either requiring cloning the shipped environment via the ArcGIS Pro GUI, or copy-pasting everything into a separate Anaconda installation’s env folder– –both of which broke during minor releases and up. This is a super great advancement for the stability of scripting environments which are becoming non-negotiable for the modern, data analytics-driven organization. I haven’t seen any tutorials on how to do this yet, so, I figured: why not write my own?</p>
<p><a href="../../img/posts/2021-03-19-esri-doc-snippet-01.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://feralcatcolonist.github.io/img/posts/2021-03-19-esri-doc-snippet-01.png" class="img-fluid" alt="A screenshot of Esri documentation announcing that arcpy is available as a conda package"></a></p>
<p><a href="https://pro.arcgis.com/en/pro-app/latest/arcpy/get-started/installing-arcpy.htm">https://pro.arcgis.com/en/pro-app/latest/arcpy/get-started/installing-arcpy.htm</a></p>
<p>First, you’ll need to download Anaconda, there are a few stipulations that may require the purchase of a commercial package. As of 03/18/2021, those are as follows:</p>
<p><a href="../../img/posts/2021-03-19-anaconda-terms-of-use.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2"><img src="https://feralcatcolonist.github.io/img/posts/2021-03-19-anaconda-terms-of-use.png" class="img-fluid" alt="A screenshot of Anaconda documentation detailing terms of use"></a></p>
<p><a href="https://www.anaconda.com/terms-of-service">https://www.anaconda.com/terms-of-service</a></p>
<p>Second, run the Anaconda Prompt as an administrator. There’s an alternative, but this is the way to avoid headaches at new releases of ArcGIS Pro. You’ll enter the following command to create a new environment:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode cmd code-with-copy"><code class="sourceCode dosbat"><span id="cb1-1">conda create -<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-name</span> whatever_you_want_your_env_called</span>
<span id="cb1-2">creating a new environment in conda</span></code></pre></div></div>
<p><a href="../../img/posts/2021-03-19-cmd-gif-01.gif" class="lightbox" data-gallery="quarto-lightbox-gallery-3"><img src="https://feralcatcolonist.github.io/img/posts/2021-03-19-cmd-gif-01.gif" class="img-fluid" alt="A gif showing the creation of a new environment in conda terminal"></a></p>
<p>Next, we’ll activate our newly created environment using the command:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode cmd code-with-copy"><code class="sourceCode dosbat"><span id="cb2-1">conda activate hopefully_you_named_your_env_something_short</span></code></pre></div></div>
<p>You have successfully entered your new environment when the parentheses to the left of the command changes from <code>(base)</code> to <code>(the_name_of_your_environment)</code>:</p>
<p><a href="../../img/posts/2021-03-19-cmd-snippet-02.png" class="lightbox" data-gallery="quarto-lightbox-gallery-4"><img src="https://feralcatcolonist.github.io/img/posts/2021-03-19-cmd-snippet-02.png" class="img-fluid" alt="An image showing the conda activate command in the conda terminal"></a></p>
<p>This really is the beauty of Anaconda. You’re able to separate your environments for the purpose they were originally created. Over time a wide variety of libraries can cause the solver to be sluggish as Anaconda tries to figure out which versions of what libraries play nice with other libraries. Package management is a surprisingly tricky problem to get around; generally, less is more when it comes to the number of libraries you push together.</p>
<p>Next! Let’s download ArcPy! We’re going to request the library from Anaconda. Every all the libraries that are available will have a unique method of requesting, sometimes they will come from the Anaconda Forge, and other times they’ll be hosted directly. If you’re wondering how to install your favorite library use a search engine and type:</p>
<p><code>Anaconda LIBRARY-NAME-HERE</code></p>
<p>Any result from Anaconda.org will provide a page that shows what command(s) are available to download the library in question. For ArcPy, that page looks like this.</p>
<p><a href="../../img/posts/2021-03-19-anaconda-site-snippet.png" class="lightbox" data-gallery="quarto-lightbox-gallery-5"><img src="https://feralcatcolonist.github.io/img/posts/2021-03-19-anaconda-site-snippet.png" class="img-fluid" alt="a screenshot of the arcpy package page on anaconda.org"></a></p>
<p>We have two options, let’s choose the top choice and enter it into our administrative Anaconda Prompt:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode cmd code-with-copy"><code class="sourceCode dosbat"><span id="cb3-1">conda install <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-c</span> esri arcpy</span></code></pre></div></div>
<p><a href="../../img/posts/2021-03-19-cmd-gif-02.gif" class="lightbox" data-gallery="quarto-lightbox-gallery-6"><img src="https://feralcatcolonist.github.io/img/posts/2021-03-19-cmd-gif-02.gif" class="img-fluid" alt="A gif showing the installation of arcpy using conda"></a></p>
<p><a href="../../img/posts/2021-03-19-cmd-snippet-02.png" class="lightbox" data-gallery="quarto-lightbox-gallery-7"><img src="https://feralcatcolonist.github.io/img/posts/2021-03-19-cmd-snippet-02.png" class="img-fluid" alt="a screenshot of a terminal message showing that the arcpy dlls are symbolically linked"></a></p>
<p>Alternatively, when you create your environment in a non-administrative Anaconda Prompt will be unable to sync the DLLs that get updated with the new releases of ArcGIS Pro. This will cause an error in your system and you’ll be unable to use ArcPy. If you do not have access to administrator credentials, then you will either remove or upgrade ArcPy at each minor release or greater.</p>
<p><a href="../../img/posts/2021-03-19-cmd-gif-03.gif" class="lightbox" data-gallery="quarto-lightbox-gallery-8"><img src="https://feralcatcolonist.github.io/img/posts/2021-03-19-cmd-gif-03.gif" class="img-fluid" alt="A gif showing installation of ArcPy in a non-admin conda terminal"></a></p>
<p><a href="../../img/posts/2021-03-19-cmd-snippet-03.png" class="lightbox" data-gallery="quarto-lightbox-gallery-9"><img src="https://feralcatcolonist.github.io/img/posts/2021-03-19-cmd-snippet-03.png" class="img-fluid" alt="a screenshot of a terminal message showing that the arcpy dlls are copied"></a></p>
<p>So there! The hard part is over. At least the confusing part. Everything else can, and should, be done in a non-administrative Anaconda Prompt shell. The ArcGIS API for Python (arcgis) will be installed as well, it is listed below for reference. In turn, enter the following commands to unlock all the up-to-date Esri good-ness. After each line, the solver will work its magic and ask for a simple y/n to proceed. This process can take a minute or more per library depending on the complexity of the interdependencies.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode cmd code-with-copy"><code class="sourceCode dosbat"><span id="cb4-1">conda upgrade arcpy</span>
<span id="cb4-2">conda install arcgis</span>
<span id="cb4-3">conda upgrade arcgis</span>
<span id="cb4-4">conda install geopandas</span></code></pre></div></div>
<p><a href="https://geopandas.org/en/stable/">Geopandas</a> is a great addition as it also comes with the <a href="https://pypi.org/project/fiona/">Fiona</a>, <a href="https://shapely.readthedocs.io/en/stable/">Shapely</a>, and <a href="https://pyproj4.github.io/pyproj/stable/">PyProj</a> libraries.</p>
<p>Now we can run a simple test to make sure everything is working correctly. The caveat for using ArcPy is the need to have ArcGIS Pro installed on your machine and meeting one of the following conditions:</p>
<p><a href="../../img/posts/2021-03-19-esri-doc-snippet-02.png" class="lightbox" data-gallery="quarto-lightbox-gallery-10"><img src="https://feralcatcolonist.github.io/img/posts/2021-03-19-esri-doc-snippet-02.png" class="img-fluid" alt="A screenshot of Esri documentation showing the conditions for using arcpy"></a></p>
<p>[https://pro.arcgis.com/en/pro-app/latest/arcpy/get-started/installing-arcpy.htm]</p>
<p>On my machine, I’m signed in automatically and also have a <a href="https://gist.github.com/FeralCatColonist/9cae407c7200fa6a45f922c019e397c3">gist</a> to keep servers signed in as well. Let’s test it! We’ll open Python through the command line and import ArcPy– –if all goes well we won’t see any errors and the next line will appear.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode cmd code-with-copy"><code class="sourceCode dosbat"><span id="cb5-1">python</span></code></pre></div></div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> arcpy</span></code></pre></div></div>
<p><a href="../../img/posts/2021-03-19-cmd-gif-04.gif" class="lightbox" data-gallery="quarto-lightbox-gallery-11"><img src="https://feralcatcolonist.github.io/img/posts/2021-03-19-cmd-gif-04.gif" class="img-fluid" alt="A gif showing a conda terminal activating python and then importing the arcpy library"></a></p>
<p>That’s it! As a bonus, we can export our environment to re-use elsewhere or to keep multiple workspaces synced together. This is done via a .yml file and will be necessary if you don’t have access to the administrative Anaconda Prompt.</p>
<p><a href="../../img/posts/2021-03-19-cmd-gif-05.gif" class="lightbox" data-gallery="quarto-lightbox-gallery-12"><img src="https://feralcatcolonist.github.io/img/posts/2021-03-19-cmd-gif-05.gif" class="img-fluid" alt="A gif showing a conda terminal exporting the environment file as a .yaml"></a></p>
<p>You’ll copy the output, enter it into your text editor of choice, and save the file as a .yml. Keep this file updated as you update your environment.</p>
<p><a href="../../img/posts/2021-03-19-conda-yaml.png" class="lightbox" data-gallery="quarto-lightbox-gallery-13"><img src="https://feralcatcolonist.github.io/img/posts/2021-03-19-conda-yaml.png" class="img-fluid" alt="A screenshot showing a fully built .yaml file for the conda environment"></a></p>
<p>Now you can re-create this environment using the command:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode cmd code-with-copy"><code class="sourceCode dosbat"><span id="cb7-1">conda env create <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-f</span> thenameofyourfile.yml</span></code></pre></div></div>
<section id="in-closing" class="level2">
<h2 class="anchored" data-anchor-id="in-closing">In Closing</h2>
<p><a href="../../img/posts/2021-03-19-ouroboros.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-14"><img src="https://feralcatcolonist.github.io/img/posts/2021-03-19-ouroboros.jpg" class="img-fluid" alt="An ouroboros in an 1478 drawing in an alchemical tract."></a></p>
<p>This latest release really is a watershed moment for creating stable business systems that are programmatically managed. Different environments can be deployed allowing a greater degree of customization in terms of feature enrichment, data cleansing, and the sundry extract-transform-load chores that define the modern enterprise. Honestly, I’m totally stoked. Too many times I’ve held off on major upgrades because of the labor involved with recreating my Python environment. Hopefully, this clears up any confusion for those uninitated with managing Anaconda environments. If you have any questions feel free to ask!, or check out Anaconda’s excellent <a href="https://docs.anaconda.com/anaconda/">documentation</a>!</p>
<p><a href="../../img/posts/2021-03-19-logo-anaconda-esri.png" class="lightbox" data-gallery="quarto-lightbox-gallery-15"><img src="https://feralcatcolonist.github.io/img/posts/2021-03-19-logo-anaconda-esri.png" class="img-fluid" alt="A mash-up logo of Anaconda and Esri"></a></p>


</section>

 ]]></description>
  <category>GIS</category>
  <category>Python</category>
  <category>ArcGIS</category>
  <category>Conda</category>
  <guid>https://feralcatcolonist.github.io/posts/2021-03-19_standalone-arcgis-conda/</guid>
  <pubDate>Fri, 19 Mar 2021 00:00:00 GMT</pubDate>
  <media:content url="https://feralcatcolonist.github.io/img/posts/2021-03-19-logo-anaconda-esri.png" medium="image" type="image/png" height="48" width="144"/>
</item>
<item>
  <title>Nowhere, Fast</title>
  <link>https://feralcatcolonist.github.io/posts/2019-05-03_nowhere-fast/</link>
  <description><![CDATA[ 






<p><em>This article was originally published in URISA’s The GIS Professional Issue 288</em></p>
<p>Düsseldorf, Germany 2008: Franz-Josef Göbel, chairman of the aid organization Alte Löwen (Old Lions), helps to pioneer the first “phantom stop”. This device, both humane and revolutionary, addresses the issue of patients with neurodegenerative disorders wandering away from care facilities. A decoy bus stop. Disoriented patients will find their way outside the care facility, spy the bus stop, sit there, and wait. And wait. In an area of the world with robust public transit, the natural assumption is that the bus will come; that it doesn’t, creates an invisible corral where patients feel secure and in control of their ultimate terminus. There a person waits, letting their body cruise in autopilot as their mind embarks on travels that likely they’ll never make. Here they idle until a facility worker can tow them back, or they return of their own recognizance. Physical restraints are replaced by mental roadblock and immobilizing straps now become a pilgrim’s mirage, mistaking journey for destination. The trajectory of our own professional development can often be de-railed through similar illusions of security– –enclosures of complacency.</p>
<section id="the-longest-wait" class="level2">
<h2 class="anchored" data-anchor-id="the-longest-wait">The Longest Wait</h2>
<p>Finding my first job was a Sisyphean task. Tailoring my applications and cover letter for the hopes of getting an interview, interviewing, and then, radio silence. In those days of desperation, the idea of breaking into the industry seemed mythic. Eventually, I landed an opportunity digitizing cadastral records.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../img/posts/2019-05-03-map-bus-stop-01.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Closed Bus Stop along Eldorado Pkwy, McKinney Texas"><img src="https://feralcatcolonist.github.io/img/posts/2019-05-03-map-bus-stop-01.png" class="img-fluid figure-img" alt="Closed Bus Stop along Eldorado Pkwy, McKinney Texas. A minimal map is super-imposed on top of the image"></a></p>
<figcaption>Closed Bus Stop along Eldorado Pkwy, McKinney Texas</figcaption>
</figure>
</div>
<p>For the first time in my adult life, I had a semblance of stability. Naturally, I immediately mapped out a life plan. Eerily, it looked similar to the expectations for any employer’s fabled entry-level position:</p>
<ul>
<li>3-5 years’ experience</li>
<li>Web/Server knowledge</li>
<li>Programming expertise in multiple languages</li>
</ul>
<p>The issue, as I came to realize it albeit several years later, was that I had done very little to achieve any of these goals. Other than, of course, waiting. I went to work every day, left at the end of my shift, and spent the weekends laid about, as if I deserved nothing more than to rest after a week’s worth of mind-numbing work.</p>
<p>This realization came quite unexpectedly when the company for which I worked had several bouts of hiring. Fortunately, I had a boss who saw value in both delegating and the collaborative process. We were expected to sift through the hundreds of applications submitted, rank applicants based on our own personal subjective scale, and then reconvene with a final vote as to who should get invited to interview in-person. This consensus hiring continued into the interviews, which were an entirely different creature. Often, those that had the most experience on paper could not articulate core technical concepts outside of their daily wheelhouse. When asked about their motivation or future aspirations, the response was something habitual about “liking to make maps”, “enjoying the cartographic process”, or “being good with computers”. I suppose it is somewhat difficult to get excited about cadastral record-keeping; however, I imagined myself on the other end of the interview process and found my own answers to be equally lackluster.</p>
<p>I had been spoiled with a promotion a year since my initial hiring but this forward momentum began to falter after our manager left. A replacement was appointed from within our team without a competitive process– –which was not unusual considering the size of the company and the relatively clear lines of hierarchy. Team consensus was abandoned, new positions were opened and appointed. Suddenly it seemed that the metaphorical emergency brake had been applied full force. The months dragged on. I watched as my more ambitious peers moved on to other companies.</p>
<p>I felt bound by my own lack of imagination. I had spent years grinding away, gaining “experience”, but in reality, I had done very little to progress. I found myself at my own phantom stop. I had been pacified by the monotony of my own work, sitting at my desk imagining that each day brought me closer to advancement, the next job, a corner office with a placard on my desk. The harsh reality was that the bus I was waiting for was no longer running; that line had ceased regular service even before our profession was defined by a graphical user interface. The idea that somehow being next in line, or that time served was enough to propel a career, to a destination that I hadn’t even bothered to articulate, was an illusion born of inexperience and wishful thinking</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../img/posts/2019-05-03-map-bus-stop-02.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Closed Bus Stop along Wilson Creek Pkwy, McKinney Texas"><img src="https://feralcatcolonist.github.io/img/posts/2019-05-03-map-bus-stop-02.png" class="img-fluid figure-img" alt="Closed Bus Stop along Wilson Creek Pkwy, McKinney Texas. A minimal map is super-imposed on top of the image"></a></p>
<figcaption>Closed Bus Stop along Wilson Creek Pkwy, McKinney Texas</figcaption>
</figure>
</div>
</section>
<section id="forward-progress" class="level2">
<h2 class="anchored" data-anchor-id="forward-progress">Forward Progress</h2>
<p>Identifying a problem is not a solution in itself, but it does allow you to formulate a remedy. I began interviewing. Admittedly, interviews were much easier to acquire with the requisite years of experience that I had gained to date. Having been on the driving end of several dozen interviews, the exchange seemed more mundane as I better-understood business requirements and their relation to spatial technologies. At that point in my career, it would be disingenuous of me to say that I understood accurately how to treat the malaise of idleness.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../img/posts/2019-05-03-map-bus-stop-03.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Closed Bus Stop along Red Bud Ave south of Virginia Pkwy, McKinney Texas"><img src="https://feralcatcolonist.github.io/img/posts/2019-05-03-map-bus-stop-03.png" class="img-fluid figure-img" alt="Closed Bus Stop along Red Bud Ave south of Virginia Pkwy, McKinney Texas. A minimal map is super-imposed on top of the image"></a></p>
<figcaption>Closed Bus Stop along Red Bud Ave south of Virginia Pkwy, McKinney Texas</figcaption>
</figure>
</div>
<p>Frankly, it was not until my current position that I began to adequately conceptualize habits that contribute to forward progress. As much as I appreciate a top ten list, for me, there are only two salient actions: getting your foot in the door and being able to make an impact once you’re there; or in other terms, pairing networking with research and development. Previously, I had stumbled on elements of each but never had pursued the broader themes in any deliberate sort of way. As a consultant, my job was essentially to network. Learn their name, understand the situation from their perspective, solve problems, smile when you talk, be both pleasant and approachable. Our services were on retainer, no penalty contracts; we retained clients based on how we treated them.</p>
<p>The idea of attending professional events, presenting at conferences, writing articles, or setting up a regional Slack channel, is deeply unnerving to me. Almost mechanically, I force myself outside of my comfort zone; ask questions at the end of presentations, introduce myself to strangers, keep business cards on hand, and practice my 10 second response to “what do you do in your position?”. The result is somewhat comical as I battle to hear myself and others over a pounding heart, rushing blood, and a near constant stream of internal dialogue. This a set of skills that I neglected to develop outside of a professional client-consultant relationship; however, it is something that can be taught and learned. Maintain eye contact, don’t overpower a group conversation, and be comfortable with listening.</p>
<p>Rote actions will appear as hollow as they feel when they lack appropriate intent. A hammer for the sake of the object itself is only a showpiece; the utility materializes in the presence of a nail. When meeting new people, or reconnecting with acquaintances, my goal is always to learn. Every individual has expertise gained through virtue of their position, their work environment, and their life experiences. People are generally interested in discussing things that they know; in most cases, people will discuss themselves. This dynamic works well with patience, practice, a few leading questions, and an easy pivot. “What are you hoping to implement in the near future”, “what’s been the most challenging project”, “how did you implement that”, and “why is that”. Cultivating a genuine desire to explore and discover the stories of others has catalyzed my new conception of networking.</p>
<p>Additional positions were predicated on some of the unique experiences that I had acquired as part of the official workday at my first job. Client site visits for sales and installations, web service administration and application building, and some light automation. It was beyond fortuitous that a first position would span such a wide vertical integration of skills; the second position was far narrower. The pay was better but contractual, the company was national, and suddenly I felt expendable. The notion of a “safe” job had evaporated, along with it, my complacency. I began spending all my free time developing the traits that made me marketable.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../img/posts/2019-05-03-map-bus-stop-04.png" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Closed Bus Stop along Red Bud Blvd north of US 380, McKinney Texas"><img src="https://feralcatcolonist.github.io/img/posts/2019-05-03-map-bus-stop-04.png" class="img-fluid figure-img" alt="Closed Bus Stop along Red Bud Blvd north of US 380, McKinney Texas. A minimal map is super-imposed on top of the image"></a></p>
<figcaption>Closed Bus Stop along Red Bud Blvd north of US 380, McKinney Texas</figcaption>
</figure>
</div>
</section>
<section id="destination-ahead" class="level2">
<h2 class="anchored" data-anchor-id="destination-ahead">Destination Ahead</h2>
<p>My research and development (R&amp;D) time was exclusively during lunch hours. Although restricted to the technology that the organization currently used, I was more than free to use our software to test new workflows, run different analyses, and prepare articles and presentations. Outside of specific software applications, I organized my lunch hours around interesting webinars and explanatory YouTube videos covering a variety of topics. And I read. Constantly. The wide availability of both digital and print materials makes most subjects accessible to those that are willing to spend the time to seek them out.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../img/posts/2019-05-03-map-bus-stop-05.png" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Closed Bus Stop along Community Ave, McKinney Texas"><img src="https://feralcatcolonist.github.io/img/posts/2019-05-03-map-bus-stop-05.png" class="img-fluid figure-img" alt="Closed Bus Stop along Community Ave, McKinney Texas. A minimal map is super-imposed on top of the image"></a></p>
<figcaption>Closed Bus Stop along Community Ave, McKinney Texas</figcaption>
</figure>
</div>
<p>Sacrificing personal time and pursuing R&amp;D outside of working hours has several distinct advantages. Chief among them, working with other software and versions that are unapproved. In the Esri realm, your organization may not have certain extensions or be several years behind the current software release. An ArcGIS Home-Use license costs $100 per year and comes fully loaded with all extensions, allowing the ability to hone skills that you would not otherwise have access to, and remain on the cutting edge of functionality. Outside of Esri products, there is an entire world of spatial technology not commonly used in American workplaces. QGIS is free and delivers the most up-to-date spatial functionality, including working in 3D; it also natively uses modern file formats like the GeoPackage. Moving beyond graphical interfaces, any sort of programming is made easier with unfettered admin rights. Python and R deliver incredibly fast, capable, and visual solutions to geospatial problems; paired with Project Jupyter, programming is becoming more collaborative and approachable to a wider audience.</p>
<p>As providence would have it, my current position quickly followed my second. Here, my interests are closely aligned with those of the organization: use spatial technologies to enable efficiencies and analysis-driven decisions. I had spent years effectively sitting on a bench, waiting for something to come and deliver me to a future that was nebulous and undefined. When I finally woke from my self-induced daydreams, I was startled into action, but without a clear destination in mind. I wasn’t sure where I wanted to go, but I had an idea of the skills that I would need to be successful.</p>
<p>My current organization is aggressively pursuing a transformation into a High Performance Organization (HPO). As such, opportunities abound to study organizational dynamics, performance, and agility. Among other things, this has impressed upon me the importance of a structured R&amp;D program for employees. The idea that knowledge workers are only as effective as the knowledge that they possess, makes imperative the continuing acquisition of knowledge and skills. Today’s high-octane pace of technological change argues that the stagnant mind becomes increasingly outdated and obsolete. Hiring an employee without a training infrastructure is akin to purchasing a vehicle without a thought given to refueling it. The cost of doing business includes investing in the continuous education of your workforce. Acceptance of this new reality, like many things, is a spectrum. While few of us may be in a position to dictate organizational policies, we all have the agency to pursue personal growth and influence others through our example.</p>
<p>My journey continues. Within my team, our measures of success involve setting aside time for R&amp;D; we use the term “Q2”, short-hand for Quadrant 2 from the Eisenhower Matrix and are expected to dedicate a minimum of 10% of our working hours towards this goal. In addition to this, I still set aside my lunches for reading and webinars. The more that I learn, the better idea that I have about what else I should know. And the wider that I branch out, I find myself less satisfied with my current level of expertise.</p>
<p>In life, destinations require planning and measurable effort. The bus isn’t real. There does not exist a vehicle, actual or metaphorical, that will spirit you away into a rosy and productive future. Our personal phantom stops may not be physical but their effects are tangible. Often times it is difficult to discern these mental decoys because they are so comfortable. We’re bored. Cruising on autopilot. Waiting passively for something to happen for us. At the heart of this illusion of security, a lack of skills development and refusing to learn from others. While these mental enclosures may be a figurative shelter of sorts, the world continues moving forward. And the complacent wait. And wait.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../img/posts/2019-05-03-map-bus-stop-06.png" class="lightbox" data-gallery="quarto-lightbox-gallery-6" title="Closed Bus Stop along McKinney Ranch Pkwy, McKinney Texas"><img src="https://feralcatcolonist.github.io/img/posts/2019-05-03-map-bus-stop-06.png" class="img-fluid figure-img" alt="Closed Bus Stop along McKinney Ranch Pkwy, McKinney Texas. A minimal map is super-imposed on top of the image"></a></p>
<figcaption>Closed Bus Stop along McKinney Ranch Pkwy, McKinney Texas</figcaption>
</figure>
</div>
<p><em>Unintentional Phantom Stops</em> a Photo-Map Collection by Jordan Carmona, GISP</p>
<p>Here are a series of transit shelters throughout the city that currently sit unused. They are in different stages of disrepair, depending on the area of town and stewardship by adjacent property owners. On my commute, I pass the same shelter. I continue to be surprised by the feelings of loss, desolation, and longing that it evokes. That became the seed that grew into words. With the passing of conventional transit strategies, my hope is that these relics are repurposed into something more useful; I would hate to see them demolished as if this section in our history had never happened.</p>


</section>

 ]]></description>
  <category>Career</category>
  <category>Photography</category>
  <category>Cartography</category>
  <guid>https://feralcatcolonist.github.io/posts/2019-05-03_nowhere-fast/</guid>
  <pubDate>Fri, 03 May 2019 00:00:00 GMT</pubDate>
  <media:content url="https://feralcatcolonist.github.io/img/posts/2019-05-03-the-gis-professional-cover.png" medium="image" type="image/png" height="52" width="144"/>
</item>
<item>
  <title>A Perspective on Cartography. and The Location Advantage</title>
  <link>https://feralcatcolonist.github.io/posts/2018-08-16_food-for-thought/</link>
  <description><![CDATA[ 






<p><em>This article was originally published in URISA’s The GIS Professional Issue 284</em></p>
<p>The idea of distance learning is, historically speaking, relatively new. In the late 1720s, a teacher, Caleb Phillips, advertised lessons on shorthand note taking offered by mail; this correspondence course was completed through the exchange of letters, or correspondence, between the student and teacher. This form of education was popularized through the expansion of the increasingly ubiquitous and effective US Postal Service. By the mid-1800s, this style of learning had spread throughout Europe and the UK’s University of London boasted the first courses at the university level. Technological advances in the early 1900s saw the creation of e-learning, with courses being disseminated using radio and television broadcasts, and in the late 1900s broadband enabled internet-based platforms. Coined in 2008 by Dave Cormier, University of Prince Edward Island, the newest iteration of distance learning is the idea of the massive open online course (MOOC); the trend is towards a free, non-credit course, available to anyone with an internet connection.</p>
<p>Esri’s MOOC program sprang from their Education Outreach team; pitched in early 2014 as a way to enable users to “test-drive” the spatial analytics tools in ArcGIS Online (AGOL). The first course <em>Going Places with Spatial Analysis</em> launched in September 2014 and aimed to complement offerings by colleges and universities. In the second half of 2018, Esri now offers 5 MOOCs:</p>
<ul>
<li><em>Going Places with Spatial Analysis (Season 2)</em></li>
<li><em>The Location Advantage</em></li>
<li><em>Go-It-Yourself Geo Apps</em></li>
<li><em>Earth Imagery at Work</em></li>
<li><em>Cartography. </em></li>
</ul>
<p>The following article provides an end user’s perspective on the two MOOCs recently offered by Esri, <em>The Location Advantage</em>, and <em>Cartography.</em> (read Cartography full stop) which ran from the end of April to June.</p>
<section id="cheval-blanc-1947" class="level2">
<h2 class="anchored" data-anchor-id="cheval-blanc-1947">Cheval Blanc 1947</h2>
<p>As part of the cohort from the analog generation, I was thrilled to experience the rapid uptick of technology in the late 20th century and the excitement that each year would bring massive improvements over the last. Looking back, perhaps I was too optimistic on how quickly or efficiently we would use the seemingly un-ending bounty of tech. The first blu-ray movie that I ever saw was Pixar’s <em>Ratatouille</em>. The perceived villain of the story, Anton Ego, is a feared critic whose harsh review caused a reversal in fortunes for Chef Gusteau and his self-titled restaurant, which neatly sets up story line and plot that again culminates with the expectation of another scathing review. In this story, we’re neither the anthropomorphic hero nor his human compatriots; just prospective diners, deciding where to spend the currency that is our time and attention.</p>
<p>As a pairing, initially, I thought <em>Cartography.</em> and <em>The Location Advantage</em> complemented each other well. Map making and actionable analysis. Data presentation in two forms, the artistic and the practical. Declarative and persuasive. Coming off the heels of the SCAUG annual conference, I felt energized enough to attempt these two courses in tandem. As defined by Esri, these courses:</p>
<ul>
<li><p><em>Cover a single theme</em></p></li>
<li><p><em>Involve four to six weeks of instruction, with two-three hours per week of study</em></p></li>
<li><p><em>Require registration but are free to take</em></p></li>
<li><p><em>Run once or twice a year with firm start and end dates</em></p></li>
<li><p><em>Provide certificates to students who complete the course material</em></p></li>
<li><p><em>Introduce students to subject matter experts from across Esri and its community</em></p></li>
<li><p>The Evolution of Esri’s MOOC Program, Part IV <em>note from the future, resource links that are now dead have been removed</em></p></li>
</ul>
<p>Signing up is probably the easiest part of the process, the interface is handled through the My Dashboard section of esri.com/training; depending on your organization’s internal policies, your named user may not be “enabled for Esri access.” If so, treat yourself to a free (Essentials Plan) ArcGIS Developer account (developers.arcgis.com); it is similar to the ArcGIS Public Account but has access to premium content, premium apps, training labs, and 50 free credits a month. After signing up, the MOOCs will appear as training items in your dashboard, clicking on one will bring you to that course’s home page, and from there you “enter” the course, another dashboard-type situation.</p>
</section>
<section id="chew-it-slowly" class="level2">
<h2 class="anchored" data-anchor-id="chew-it-slowly">Chew it Slowly</h2>
<p>Like any pairing, comparisons between two items experienced concurrently are inevitable. As much as I tried to avoid it, I had a definite preference going into the MOOCs. My favorite course in school was Applied Retail GIS; case studies, ArcGIS Business Analyst Online (BAO), and applied location analytics. I was dreading a course on cartography, probably one of the weakest aspects of my professional work. As part of our organizational ethos, we strive for self-improvement– –I was determined to improve myself in this aspect. Esri has opted for a more traditional experience with the majority of their MOOCs, content is incrementally unlocked on a weekly schedule and a syllabus has been prepared ahead of time. This meant that each week I experienced the pairing anew. A tot of <em>Cartography.</em> followed by a chaser of <em>The Location Advantage.</em></p>
<section id="only-think-about-the-taste" class="level3">
<h3 class="anchored" data-anchor-id="only-think-about-the-taste">Only Think About the Taste</h3>
<p>The newest of Esri’s five MOOCs, the premise behind <em>Cartography.</em> is two-fold; one, an introduction to modern cartographic technique, and two, an introduction to map-making within the less-than-appreciated ArcGIS Pro (Pro). I had only recently devoted myself to the Dao of Pro, after completing a time-cube project for a poster presentation at the 28th Annual SCAUG Conference in San Antonio. I was curious to see how this course would reinforce what I had learned independently. I was instantly captivated. The very first video is set up as a panel-format and casual discussion between 3 of the course authors and professional cartographers at Esri. “What is Cartography?” (hosted on YouTube) was 17 ½ minutes of a conversation that was an incredibly effective way to build excitement for what could otherwise be a course titled <em>The Worst Part of Your Job.</em></p>
<p>Like any course, the first session has some housekeeping tasks that you’ll need to complete. Opening exercises get you to download and install Pro, and provide you with a named user account for AGOL (and Pro) specifically for this course. After these have been squared away, the last task is to make our first map. After downloading a pre-prepared dataset, the exercise has you move through the major steps of map-making: metadata, setting a coordinate system, importing data, definition queries, symbolization, labeling, layout creation, and exporting. All of these steps are documented well and allows anyone that has never used Pro the ability to take it out for a long test drive.</p>
<p>This first exercise took a lot longer than I had originally budgeted my time for. When I thought I had reached the end of the exercise, I was greeted with a series of three stretch goals. Tasks to make the map more unique; suggestions with no steps or walkthrough. Afterward, you’re encouraged to post an introduction to a forum that is specific to the first week. Week 1 really gave me something of a reality check about the time requirements for this course. On the course homepage it says, 2-3 hours of study a week is required. I’d say that 3+ hours is a more reasonable estimate. For a subject like cartography, this is a good thing.</p>
</section>
<section id="now-try-them-together" class="level3">
<h3 class="anchored" data-anchor-id="now-try-them-together">Now, Try them Together</h3>
<p><em>The Location Advantage</em> is the second MOOC that Esri launched in 2015, and geared specifically for recent graduates and current students of business schools at the bachelor and graduate level. Similar to <em>Going Places with Spatial Analysis</em>, the course allows students to “try on” BAO. This is accomplished, again, with a named user given for the duration of the course. The flavor of this course is decidedly different. The opening video (hosted directly through Esri’s site) feels more like a prolonged voicemail rather than an invitation to explore; it is thankfully short.</p>
<p>Outside of the housekeeping tasks, on the list of exercises for the week, we had several items that caught my eye: additional videos with Esri staff, a case overview, and a weekly guest lecture from an industry expert. A software developer at Esri gives a much-needed extended introduction to the concepts of how geography and location information can benefit businesses, this feels like the warm welcome that <em>Cartography.</em> nailed on the first swing. The lecturer for Week 1, is none other than University of North Texas professor, Dr.&nbsp;Murray Rice, the same instructor that taught the unforgettable course Applied Retail GIS. I felt right at home and excitedly began to devour the content. Unfortunately, it felt like as soon as the “lecture” started it was over. The interview was done over the phone, so a single slide is presented during the entirety of the conversation. The host asks four complete rounds of questions and then calls it a day.</p>
<p>The case overview video briefly introduces the actual exercise, giving it more of a narrative feel: you work for a fictitious Canadian Bank that wants to increase the services offered. In this exercise there is not an actual BAO component, everything is done within AGOL. Your task is to locate company buildings from a .csv, load them into a web map, alter pop-up settings, alter symbology, add demographic data from AGOL layers, and author a swipe/spyglass story map to consume your work. Documentation is great, and those unfamiliar with AGOL should fly along without a problem.</p>
</section>
</section>
<section id="you-must-try-things-that-may-not-work" class="level2">
<h2 class="anchored" data-anchor-id="you-must-try-things-that-may-not-work">You Must Try Things that may not Work</h2>
<p>Over the next 6 weeks, the time-demands were pretty significant. If you have the ability, I would whole-heartedly recommend carving out research &amp; development time during your working day. Doing more than treading water in the ever-expanding ocean that is spatial technology requires us to deliberately increase our skill set by trying out processes and discovering new ways to do things better. My R&amp;D time was largely spoken for, which is why I opted to spend my lunch hour completing the MOOCs, even still, I ended up doing some of this work at home as well. In the end, what was left?</p>
<p>Understanding the Esri MOOCs requires understanding MOOCs in general. The biggest deficiency of these programs happens to be their best selling points, low barriers to entry and accessibility from anywhere form a disconnect between the student and the material. Unlike compulsory education, there is no school building that you go to daily, no teacher to reprimand you for failure to meet expectations, and no peer pressure to complete the learning journey together. While MOOCs enjoy high rates of registration, completion rates lag far behind. The Esri MOOCs are not exempt from this trend, using viewing rates from the <em>Cartography.</em> videos hosted on YouTube as a proxy, the approximate attendance rates for Week 1 started at 20,332 and finished Week 6 at 3,440. While the 83% decrease overall seems shocking, the largest numerical drop is between Week 1 and Week 2 (6,088); if viewed from Week 2 to Week 6 the attrition rate is a more realistic 44%.</p>
<p>Esri has opted for a more traditional experience with the majority of their MOOCs, content is incrementally unlocked on a weekly schedule and a syllabus has been prepared ahead of time. Dialogue is mostly uni-directional; although, there are some open forums during some weeks of the MOOCs (course instructors participated!), most of the time a student is offered a one-and-done response to finish the section each week. Each course did include an ask-me-anything session hosted on Esri’s GeoNet, the participation in each course varied dramatically with 18 comments in <em>The Location Advantage</em> and 165 comments for <em>Cartography.</em></p>
<section id="lightning-y" class="level3">
<h3 class="anchored" data-anchor-id="lightning-y">Lightning-y</h3>
<p>Cartography, full stop. It would be easy enough to end it here. As the fifth iteration of Esri’s MOOC program, <em>Cartography.</em> enjoys the benefits of high production values. Apart from the magnetic on-screen talent, video is captured using multiple shots, audio is professionally recorded, and pains are taken to reduce the apparent use of scripts and talking points. The syllabus and materials are well-thought out, there are multiple links to additional examples and further reading, and two exercises a week. The weekly video discussion is the show that I wish I could watch on Netflix (and maybe Netflix agrees?). It has a simple introduction and theme with background music, appropriate B-roll throughout, the cartographers are informative, enthusiastic, and easily connect with the audience while discussing their passion. Finally, recurring segments give the series continuity from week to week: word of the week, carto-fails, famous cartographer, analog tools of the cartographic trade, and a weekly tea with biscuits. And lastly, the stretch goals felt appropriate; less intimidating than a blank canvas, but enough to feel like you’re building ownership for the project.</p>
<p>The ability to use BAO in an unlimited way is hands down the best perk of <em>The Location Advantage.</em> Other notable benefits included the lecture videos with Esri employees, and the phone interviews with academic leaders in business, both of which were nice ways that the course delved into areas beyond how to simply use particular tools within the BAO environment. These conversations helped to show the necessity of approaching business objectives with a geographic lens in your toolkit. Further, each of the exercises has a scenario in which you are asked to imagine that you are part of a company or have some sort of economic interest in a specific topic; for me, these helped to spur my imagination into industries and use-cases with which I am unfamiliar. The greatest benefit shared between both courses is that all materials are downloadable in PDF format including exercises and video transcripts.</p>
</section>
<section id="anchovy-licorice-sauce" class="level3">
<h3 class="anchored" data-anchor-id="anchovy-licorice-sauce">Anchovy Licorice Sauce</h3>
<p>Anton Ego speculates that, negative criticism is fun to write and to read; my personal interest is in identifying ingredients that do not work in the overall MOOC dish. While Esri is offering a meaningful piece of itself, in terms of both time and resources required to produce these courses, their end goal is to increase the use of their platform and services. Although these courses are technically free, there is a significant commitment in terms of time and mental energies expended to follow through to completion; ultimately, it is the end-user that must be satisfied and fulfilled by the exchange.</p>
<p>While <em>The Location Advantage</em> was only produced in 2015, it has an incredibly dated look and feel. Video segments of the host and guest lecturers from within the Esri organization are shot from tripod mounts and possess no differentiation in terms of angle, zoom, or other fundamental spices from the videographer’s kitchen. Scripts were un-memorized, read verbatim, and clearly visible from the reflection of eye glasses of some lecturers. These videos were not hosted through YouTube and were affected by a technical outage during the course, making them inaccessible for a time. These are all hurdles that Esri has easily cleared in the <em>Cartography.</em> course and there seems to be little reason to not revisit the video production pieces in this instance. I had that feeling of regretting the meal I had ordered, and lusting after the plate at the table next to mine.</p>
<p>What should have been the best part of <em>The Location Advantage</em> ends up being its most lackluster, the so-called “guest lectures” with academic business leaders. These guest lectures are actually phone interviews, in each segment, the host asks 3-4 rounds of questions, and concludes the interview. Production-wise, there is nothing else to stimulate the viewer except a power point card showing the interviewee’s name, institution, and photo. While an in-person taped interview would be easy for a global company to manage, at a minimum, the effective use of B-roll would go a long way to making these conversations seem like more than an afterthought. Those that are taking this course are interested in the subject material, and the target audience is paying or has paid tens of thousands of dollars to learn more about the subject matter in business schools across the continent; the brevity of each interview is akin to baking a delicious meal, only to serve your patron a tablespoon’s worth, and expecting them to be satisfied.</p>
<p>The hosting of forums per week within the in-house educational platform feels very limited. An inability to search forum posts and limited screen real estate are obvious barriers to fulfilling participation. GeoNet is a great platform and could be used more effectively for course communication; although it would cause some of the content to bleed out beyond registrants, an unexpected benefit is to whet the appetite for the next course offering. Esri’s method of rewarding the most active users of the forum (with a free personal license or Esri published title) feels a bit forced, and seems to encourage over-commenting in lieu of substance– –perhaps guided discussions from the course teachers could compensate, or grouping participants into clusters to break down the overwhelming feeling of anonymity. Spending your temporal currency dining out should have similarities to the physical experience: charming service, stimulating conversation, and an intimate atmosphere.</p>
</section>
</section>
<section id="hungry-for-more" class="level2">
<h2 class="anchored" data-anchor-id="hungry-for-more">Hungry for More</h2>
<p>I will admit that taking two courses concurrently was a bit challenging, and perhaps not something that I will repeat; but it’s hard to say no to a great pairing. In terms of time spent, I learned things that I did not know, that I had forgotten, and had never considered. The technical aspects were great in terms of learning more about functionality that I had not yet explored in ArcGIS Pro and Business Analyst Online, and from that view, these courses were a wild success. The ability to learn about a technical subject and to have fun while doing it is the sort of privilege that is rare amongst most professionals, and meeting face-to-screen with some of the giants in the industry is even better. I am looking forward to seeing Esri continue to dedicate resources to producing original content, keeping existing courses current, and hopefully extending some screen time to individuals outside of their immediate organization.</p>
<p>The climax of <em>Ratatouille</em> sees the food critic Anton Ego discover the true identity of the famous new Chef at Gusteau’s, and despite this, write a surprisingly unabashed endorsement of the restaurant. In our conclusion, the reviews are mixed. Many aspects were well-thought out, creative, and contribute to the continuing evolution of MOOC best practices. Other features seemed ill-prepared, unimaginative, and posed a general disregard for the appetite of the intended audience. I will return again, and my expectations will be high.</p>
<p>The people at Esri are enthusiastic about their work and that enthusiasm deserves to be shared as widely as possible; I’d like to thank them for stepping out and taking risks with distributing their work in this fashion. The next round of MOOCs will launch September 5 and run through October 16, on the menu is <em>Cartography.</em> and <em>Do-It-Yourself Geo Apps.</em> Another two courses will be available starting October 31 and ending December 11, <em>Going Places with Spatial Analysis (Season 2)</em> and <em>Earth Imagery at Work.</em></p>


</section>

 ]]></description>
  <category>AGOL</category>
  <category>ArcGIS</category>
  <guid>https://feralcatcolonist.github.io/posts/2018-08-16_food-for-thought/</guid>
  <pubDate>Thu, 16 Aug 2018 00:00:00 GMT</pubDate>
  <media:content url="https://feralcatcolonist.github.io/img/posts/2018-08-16-the-gis-professional-cover.png" medium="image" type="image/png" height="62" width="144"/>
</item>
<item>
  <title>The IKEA Effect and AGOL COTS Applications</title>
  <link>https://feralcatcolonist.github.io/posts/2018-04-27_some-assembly-required/</link>
  <description><![CDATA[ 






<p><em>This article was originally published in URISA’s The GIS Professional Issue 282</em></p>
<p>Universal human behaviors are often exploited through commercial means long before they can be articulated through proper scientific study. In the 1950s, Gillis Lundgren, an employee at a fledgling Swedish mail-order business, pioneered flat-pack, ready-to-assemble furniture resulting in wide-ranging benefits: the ability to hold more inventory on site, cost-savings on assembly, easier consumer transportation of purchased goods, and easier commercial bulk shipments. Today, that mail-order business is IKEA, the world’s largest furniture retailer, with an annual global revenue in excess of 35 billion Euros. Although the business case for flat-pack furniture seems straightforward; Norton et al.’s <em>The IKEA effect: When labor leads to love</em> articulates a deeper psychological mechanism underpinning the success of ready-to-assemble products aptly termed the “IKEA effect”.¹ Broadly, this describes how consumers value items they help assemble at higher levels than identical products assembled by others. Understanding and recognizing this cognitive bias can lead the average GIS professional to produce more polished, effective applications utilizing custom-off-the-shelf (COTS) applications available in the ArcGIS Online (AGOL) ecosystem.</p>
<section id="the-ikea-effect-unpacked" class="level2">
<h2 class="anchored" data-anchor-id="the-ikea-effect-unpacked">The IKEA Effect Unpacked</h2>
<p><em>The IKEA effect</em> was based on the analysis of a series of experiments where certain participants constructed and bid on IKEA storage boxes or folded origami, while others bid solely.¹ These experiments measured value by comparing willingness-to-pay against the different classes of products assembled by participants and assembled by others, with the caveat that the assembly process must be fully completed.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../img/posts/2018-04-27-ikea-effect-article-experiment-a-b.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Establishing the IKEA Effect and its Magnitude - Experiment 1A &amp; 1B"><img src="https://feralcatcolonist.github.io/img/posts/2018-04-27-ikea-effect-article-experiment-a-b.png" class="img-fluid figure-img" alt="an image from the article Establishing the IKEA Effect and its Magnitude, from the section depicting experiment 1A and 1B"></a></p>
<figcaption>Establishing the IKEA Effect and its Magnitude - Experiment 1A &amp; 1B</figcaption>
</figure>
</div>
<p>These first two experiments focus on identifying the existence of higher valuation for self-assembled products and finding relative benchmarks for how much higher those valuations are in comparison to different standards. Experiment 1A shows that participants were willing to pay a 63% premium for a simple box that they had constructed themselves, as opposed to an otherwise identical box that was pre-constructed. This is the broadest sense of the IKEA effect and justification for the namesake. Perhaps even more interesting was the next experiment measuring magnitude.</p>
<p>First, participants, termed builders, were asked to create an origami frog or crane, and offered a chance to purchase their own creation. Second, a group of experts constructed the same types of origami creatures. Third, a different set of participants, termed others, were asked to bid on both sets of works– –setting a market price for papercraft animalia ranging the spectrum from amateurish to expert. The resultant average bids ranged from $0.05 - $0.27; builders valued their artistic endeavors at a premium of 23 cents, while others were willing to pay the paltry sum of a nickel for the self-same creations, and consummate origami was priced at 27 cents. The acute implication of this data is that builders without fundamental instruction, rote, or preparation imbue value into their products at comparable levels to the market rates for expert-crafted products.</p>
<p>At first glance, it may seem odd to evaluate the cognitive biases exploited in the creation of consumer goods; however, there are parallels to trends within the geospatial technology. The increasing prevalence of software as a service (SaaS), has shifted the focus of geospatial technology from desktop-based programs to distributed cloud-based applications. The consumerization of software users outside of the initial purchase and licensing is mostly achieved by aggressively offering online, custom-off-the-shelf (COTS) applications. The GIS professional’s interactions with these COTS applications mirrors the ready-to-assemble furniture experience; minimal instructions, standardized parts throughout an ecosystem, and lightweight components requiring no technical expertise. If cloud COTS applications mimic the flat-pack workflow, how do we make the most of our understanding of the IKEA effect?</p>
</section>
<section id="built-to-last" class="level2">
<h2 class="anchored" data-anchor-id="built-to-last">Built to Last</h2>
<p>The worst web maps I’ve ever had the pleasure of seeing were made by me, I have also built a spectacular end table that holds the litter box for my cat. The more honest truth is the cat-box seems to be coming apart at the hinges, and requires a weekly kick to straighten it out; my web applications suffer the most when I don’t take the time to polish the end-user experience. At home, my feline customer doesn’t complain about the architectural deficits introduced in the assembly of the facilities. At work, my potential customer base includes 180,000 residents, the working population and any visitors or guests that the City may see on a given day. The following are some selected strategies to employ in order to polish and more effectively utilize COTS products available in ArcGIS Online (AGOL).</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../img/posts/2018-04-27-filter-cat-litterbox-mini.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Don’t “Deploy in Minutes”"><img src="https://feralcatcolonist.github.io/img/posts/2018-04-27-filter-cat-litterbox-mini.jpg" class="img-fluid figure-img" alt="an image of a flat-pack cat box that doubles as an end table, a cat named Miniature Cat (Mini), is walking out of the entrance, a filter is applied to make it appear hand-dawn"></a></p>
<figcaption>Don’t “Deploy in Minutes”</figcaption>
</figure>
</div>
<p>One of the greatest strengths of the COTS paradigm is the inclusion of What-You-See-Is-What-You-Get (WYSIWYG) preview capabilities. These applications allow you to visualize what the end-user sees when interacting with your product. This is not a frill, it’s an opportunity to preempt embarrassing phone calls, emails, and social media posts from the viewer-base. The temptation to stand up an application, in mere minutes often sacrifices the chance to create a great experience for the end-user, one that is unencumbered by distractions created through a sloppy delivery.</p>
<p>Akin to reading the instructions before assembly, a standardized checklist should be an integral part of the development process before any COTS application is floated either internally or externally. Cartographic conventions aside, this usually starts with your Web Map item and configuring the pop-ups. From legacy attribute architecture to the 10 character limit of the dreaded shapefile, your <em>&lt;Field Alias&gt;</em> needs to be different from your <em>&lt;Field Name&gt;</em>; concise, explanatory, and use spaces. Don’t display attributes you don’t need, this includes ObjectID and shape fields. Get rid of the thousands separator option. Omit unnecessary pop-ups, if it’s the only thing on the map and has a unique symbology, this would be an excellent item to put into a legend.</p>
<p>Metadata is helpful to your colleagues, other GIS professionals, and your future self. There are now subtle prompts directing you to fill in metadata in the description page of your <em>Web Mapping Application, Web Map, Dashboard, Feature Layer, etc.</em> A summary should be at least 30 words, and accurately describe the purpose of your map. The description should be more than 100 words, and might go further into detail about the types of layers within the application, update schedules, links to other applications in the AGOL ecosystem, and other pertinent information. Tags will help others find your work, and thinking about how to tag your own work can lead you to other sources of inspiration for the problem you’re trying to tackle.</p>
<p>For the applications themselves, throw in your logos/branding, standardize the color scheme between applications and make sure that you link back to your main site. Your tagline doesn’t have to be “with Web AppBuilder for ArcGIS” or “A story map”. For the more narrative-driven applications, spelling and grammar become important considerations. While you may not be a wordsmith, there are plenty of free applications and browser plug-ins to ferret out the most major infractions; I’d recommend giving Grammarly² a go for basic spelling and grammar, or the Hemingway Editor³ if you suffer from being too rambling and verbose.</p>
<p>Despite the straightforwardness of the phrase, face-value is difficult to assess under the dual influences of the IKEA effect and the usual intimacy of a GIS professional with the data in which they work. Developing standardized ways of interacting with COTS applications will help ensure that the end-user experience is as uniform as possible between each visit and across differing applications hosted by your organization. At the end of the day, I shouldn’t have to kick the catbox, I should have built it correctly out-of-the-box. In like fashion, robust standard operating procedures should keep the wobbles out of your web apps.</p>
</section>
<section id="display-with-pride" class="level2">
<h2 class="anchored" data-anchor-id="display-with-pride">Display with Pride</h2>
<p>Interaction with the AGOL COTS ecosystem exposes us to the IKEA effect, but unlike the average consumer, our shoddy constructions are not safely sequestered within the halls of a private domicile, they live in the cloud, available to anyone across the planet with an internet connection. This makes it imperative that we identify and resolve our psychological predispositions to value work at a level higher than its face value. While we may gloss over the imperfections inherent in our own works, the end-user is unlikely to be as forgiving; our perceived value closer to a nickel than a quarter.</p>
<blockquote class="blockquote">
<p>“Don’t try to get all fancy…it doesn’t work.”</p>
</blockquote>
<p>The word “fancy” brings up about 20 results in my inbox, this is one of them. I’ve also gathered other gems like “interactive map is horrible” and “map is useless”. In all fairness, they were right. The map in question had an issue that made the selection radius horribly small, a handful of pixels. Less than noticeable on a desktop computer, glaringly obvious on any mobile device. This experience opened my eyes and let me see the unwieldy, rickety applications that I had proudly deployed.</p>
<p>This same critique can be applied to the ever venerable All-In-One map. If your map has 30 layers, you’re not doing anyone a service by hosting it. Disparate data needs to be presented with the proper context, single subject, focused applications should be the rule of thumb. In a similar vein, just because you can add a dashboard doesn’t mean that you should. Your data tells a story, it does so in specific ways. Averages and on-the-fly calculators don’t mean anything if the data they crunch was not significant to begin with.</p>
<p>I’ve heard it said a few different ways, usually a variation of, if X can’t use it, then you need to make it simpler. Be careful to casually cast a net involving X-type of person, the more accurate advice is to ask your audience to test your application. This is easiest if your audience is limited or exactly known. If your audience is nebulous or too broad, go and ask subject matter experts, GIS colleagues, laypersons, and those you would consider to be computer illiterate. Each of these people will provide insight that you may not have had yourself, or find difficult to see through the spectre of the IKEA effect.</p>
<p>The general trends of the geospatial industry are not changing anytime soon, and we will continue to see SaaS play an ever larger role as data complexity deepens and so too the computational requirements to effectively analyze it. The demand for cloud-based infrastructure, out-of-the-box data science, and COTS applications will only be outpaced by the avalanche of data that we continue to produce in real-time streams through increasingly web-enabled devices. These tools are powerful, convenient, but rely on “the builder” to assemble them correctly. Our natural impulse is to over-value the fruits of our labor, even when it involves minimal expertise. Recognizing this cognitive bias allows us to control its effects. A detailed and thoughtful examination of our interactions with ready-to-assemble applications can help us pass along showroom-quality experiences to “the others” that use our products the most.</p>
<p>¹Norton, Michael I., Daniel Mochon, and Dan Ariely. “The IKEA Effect: When Labor Leads to Love.” <em>Journal of Consumer Psychology 22, no. 3</em> (July 2012): 453–460.</p>
<p>²www.grammarly.com</p>
<p>³www.hemingwayapp.com</p>


</section>

 ]]></description>
  <category>GIS</category>
  <category>AGOL</category>
  <category>ArcGIS</category>
  <guid>https://feralcatcolonist.github.io/posts/2018-04-27_some-assembly-required/</guid>
  <pubDate>Fri, 27 Apr 2018 00:00:00 GMT</pubDate>
  <media:content url="https://feralcatcolonist.github.io/img/posts/2018-04-27-the-gis-professional-cover.png" medium="image" type="image/png" height="66" width="144"/>
</item>
<item>
  <title>Taking the “I” Out of GIS</title>
  <link>https://feralcatcolonist.github.io/posts/2017-12-19_taking-the-i-out-of-gis/</link>
  <description><![CDATA[ 






<p><em>This article was originally published in URISA’s The GIS Professional Issue 281</em></p>
<p>The majority of GIS organizations are understaffed. Certainly, there are many GIS shops around the nation that have multitudes of employees, varying titles, and even various grades within a titled role; to speak nothing of the modern hardware that usually accompanies such a large workforce. My experiences in GIS tend to be on the smaller side; most of them can be described in varying flavors of: one individual, county-level dataset, older hardware, and barely enough time to deal with the workload. Add 1-6 people, 1-2 dozen more datasets, and you’re well on the way to describing a super-majority of City and County-level GIS organizations in the nation.</p>
<section id="peculiarities-of-time" class="level2">
<h2 class="anchored" data-anchor-id="peculiarities-of-time">Peculiarities of time</h2>
<p>Albeit discouraging, thinking about GIS in terms of constraints helps us better assess the return on investment of any project that we undertake. The greatest constraint that any of us face in our professional life is time. In an idealized world, a GIS team can only spend approximately 40 hours per person from their <em>productivity bank</em>, most of which is already disbursed by taking on the many maintenance and upkeep tasks that keep a good GIS humming along. How do we maximize our weekly allowance of productive, actionable hours? What do we do in the face of a deficit?</p>
<p>Our City recently ran into a rather thorny issue that threatened to be a <em>productivity bank</em>-busting expenditure; we needed suite numbers. Some numeric context: in the past 30 years, our City has gone from a quaint County-seat of 20,000 to a bustling extension of the DFW metroplex with an estimated population of more than 175,000 and encompasses nearly 68 square miles. We issue an average of 40,000 permits per year and document more than 12,000 code enforcement cases. This year we are making the transition to an all-digital workflow; based off of a combination of our parcel layer and the more than 68,000 points that make up our address layer. A permit or case can’t be handed out without a unique address from our GIS. We needed suite numbers.</p>
<p>The first one to sound the warning klaxon was Mike, one of the managers of the Code department. An intimidating man who gets the job done by thinking through the details, his desire to do the job correctly is only outpaced by his fear that it might not get done at all. Like the genesis of many daunting tasks, I immediately felt my stomach churn and my anxieties run wild as I tried to comprehend the magnitude of the problem, calculate the steps to complete this request, and the requisite working hours that I would need to feed this data monster. Questions rushed through my internal monologue:</p>
<p>· How many actual? Approximate? What percent of our total addresses? · Does this data already exist somewhere? Who would have it? · Do we need to create it? How? What datasets can help us? · Where are they physically at?</p>
<p>Punctuated between these questions, I kept coming back to the phrase, “boots on the ground.” It’s direct, conveys the need for physical observation, and with the militaristic connotation sounds a bit heroic– –at least more heroic than City work is generally considered. Sans boots, we might have been able to grab some information to create a reasonably complete set of suites, but why create an inferior dataset?</p>
<p>The year is 2017. We have mobile devices, door-to-door routing, and a GIS capable of incredible accuracy. Why would we stack all the address points on top of each other? Why not put those points exactly where they go?, on the specific part of the building in question? Why burn a few minutes of each inspector’s time looking for where they’re going, when our GIS data (read through the new software) could route them right to the front door? It may have been faster in the short-term to do the job half-right, but in the long-term, I’d be directly responsible for slowly bleeding the <em>productivity bank</em> of another department through the micro-transactions of wasted time.</p>
</section>
<section id="when-spatial-isnt-special" class="level2">
<h2 class="anchored" data-anchor-id="when-spatial-isnt-special">When spatial isn’t special</h2>
<p>The GIS solution for this project was straight-forward enough to be featured on any homework assignment focusing on field verification: group points derived from matching primary address fields, grid index features, make data-driven pages, and print. For those of us using ESRI products, the next step would be to spin up a feature service with editable layers, and then to decide between ArcCollector or the Web App Builder for the input format. However, the more I thought through the workflow, the more I had a nagging feeling that I wasn’t going to be the one doing any of this work.</p>
<p>Sixty-five grid squares and 778 potential suite addresses later, I came to a realization: this project was too big for my current workload. Sure I could work through my lunch every day for the next few months, but there wasn’t any guarantee that I’d be able to get it done in time. I still thought that boots on the ground was the best way to complete this project, but I accepted that they wouldn’t be my own two boots.</p>
<p><a href="../../img/posts/2017-12-19-city-mckinney-map.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://feralcatcolonist.github.io/img/posts/2017-12-19-city-mckinney-map.jpg" class="img-fluid" alt="a map showing the city of McKinney, Texas. It depicts the city limits, the extra-territorial jurisdiction, and a search grid for addressing"></a></p>
<p>Like many GIS professionals, I am often guilty of an obsession with novelty. The drone, the cloud, the next upgrade. Constantly chasing the horizon can cause us to neglect the wealth of resources that we have on hand– –the most important, human resources. While spatial workers may be in short supply in many organizations; field staff, especially those that participate in revenue generation, are more abundant.</p>
<p>The Code Enforcement Officers were all over the City, all day, every day. They were driving past these facilities, and at times, initiating cases involving them. These people know our geographic area in a way more intimate than our GIS staff; forgoing bird’s eye for eye-level. Who better to empower than the people most familiar with the City?</p>
<p>Another mistake common to our trade is to assume that what we do is some sort of techno-wizardry. The spatial skillset is a framework that serves as a force multiplier for organizations. We should be enabling the best practices in our respective organizations; less Atlas, more Prometheus; less author, more printing press; less autocratic, more pragmatic.</p>
<p>Understandably, this is easier said than done. Many GIS professionals are accustomed to project stewardship from inception to completion. Giving up control requires trusting other members within your organization; more importantly, it requires an admission that a GIS person is not necessarily required to perform GIS work. The sooner that these requirements are realized, the sooner your GIS productivity bank can decrease non-critical expenditures and increase your weekly allowance of actionable hours.</p>
</section>
<section id="flexibility" class="level2">
<h2 class="anchored" data-anchor-id="flexibility">Flexibility</h2>
<p>My plan was elegant, sweeping, and utterly dismissive of how Code Enforcement conducts their business. I had started my GIS career as part of a consulting firm, and as any good consultant is wont to do, I scheduled a presentation to show off the deliverables. 36x36 inch maps, printed excel sheets, and a suit jacket with khakis– –for consultants, it’s a thing.</p>
<p>I had just finished presenting my plan, each Code Enforcement Officer would spend approximately one hour a week investigating address sites. Documentation would be simple. Literally pen and paper. These pages then sent via inter-office mail to the GIS team that handled addressing.</p>
<p>In his usual no-nonsense style, Code Enforcement Mike told me, <em>yeah that’s just not possible. </em></p>
<p>I thought a 3% time expenditure across the board was modest payment for such a large initiative. Because this wasn’t my department, and I was still fairly new to the City, I carefully replied, that I didn’t have a proverbial dog in the fight. My interests lay in making sure Code got what they needed.</p>
<p>Mike pointed out, <em>this is our busiest time of year. Our guys literally don’t have any time to spare.</em></p>
<p>I hadn’t considered this. Code cases pile up in the summer. When the weather’s warm, people come outside, the grass grows, and violations abound. Our metrics are tight. All calls responded to within a day. Mike’s team never missed this goal. Their productivity bank didn’t have a moment to spare.</p>
<p><em>But I’m not saying we can’t do it. We’ve got this guy, Briggs. We hired him part-time to take care of the mosquito trap workflow and removing nuisance signage.</em></p>
<p>The urgency behind the much-publicized Zika virus outbreaks last year had us all braced for the worst. It was forward-thinking to request additional staff for this year’s mosquito season; re-prioritizing Briggs’ schedule reduced their bandwidth, but it made the address suites mapping possible.</p>
<p>Mike finished vouching, <em>this way, everything’s going to be done the exact same way.</em></p>
<p>I agreed with Mike’s assessment. On first glance, we might be losing speed, but we’d be gaining standardization; one submission format would increase the accuracy in translating the paper observations to the GIS by the addressing team.</p>
<p><em>I’m hanging this map up on my wall and we’re going to mark off each square as it comes in. I’ll keep you posted.</em> With that, we shook hands and I returned to my office.</p>
<p><a href="../../img/posts/2017-12-19-table-addresses.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-2"><img src="https://feralcatcolonist.github.io/img/posts/2017-12-19-table-addresses.jpg" class="img-fluid" alt="a page of the generated map book, it shows columns relating the address location, location, name, grid number, and page number"></a></p>
</section>
<section id="added-benefits" class="level2">
<h2 class="anchored" data-anchor-id="added-benefits">Added Benefits</h2>
<p>For the next two months, Mike’s guy, Briggs diligently mapped all the address suites throughout the City. In the end, the work was less than heroic, and it so happened that there were only two boots on the ground. In the peak heat of the summer his boots walked retail centers, his boots went into office buildings. Briggs wasn’t self-taught GIS, he never had a university course. He did his job, he drove the City, and he wrote down what he saw. At the end of the project, he had visited 218 buildings and recorded 1,362 suites.</p>
<p>Possession of a completed unique addressing layer let us continue business as usual. We met the minimum requirements for the new software. That’s good. Now, however, the increased granularity of our newly augmented address suites dataset allows door-to-door routing for emergency services to any location in the City. That’s great. Because for these crews, the micro-transactions of wasted time can easily mean lives lost. For them, Briggs shoring up their <em>productivity bank</em> is the definition of heroic. I just showed up with some maps.</p>
<p><a href="../../img/posts/2017-12-19-map-book-page.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-3"><img src="https://feralcatcolonist.github.io/img/posts/2017-12-19-map-book-page.jpg" class="img-fluid" alt="a page of the generated map book, it shows a map at a medium scale, addresses are symbolize prominently along with labels for each, the bottom of the page includes a list of the addresses shown on the page"></a></p>


</section>

 ]]></description>
  <category>GIS</category>
  <category>Local Gov</category>
  <category>Cartography</category>
  <guid>https://feralcatcolonist.github.io/posts/2017-12-19_taking-the-i-out-of-gis/</guid>
  <pubDate>Tue, 19 Dec 2017 00:00:00 GMT</pubDate>
  <media:content url="https://feralcatcolonist.github.io/img/posts/2017-12-19-the-gis-professional-cover.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Spatial Appetites</title>
  <link>https://feralcatcolonist.github.io/posts/2017-12-04_spatial-appetites/</link>
  <description><![CDATA[ 






<p><em>This article was originally published in URISA Texas’ The Lone Star Issue 3</em></p>
<p><em>Perhaps one of the most important advances of the 19th century was the advent of refrigeration; allowing the year-round production of lager beer and meat-packing. While the former is experiencing a bit of a renaissance through the rise of craft breweries, the latter has impacted the lives, laws, and health of millions of Americans for more than one hundred years. In 1905, Upton Sinclair published The Jungle, a novel that describes a fictionalized slaughterhouse/meat-packing plant in Chicago detailing the appalling working conditions and unsanitary practices that were common during that time period. For Sinclair, the saliency lay in the abuses of workers’ rights; but the average reader’s concern was more basic– –horror to discover their food was routinely adulterated with sawdust, human and animal matter. Responding to the mass outrage in the following year, the United States Congress passed two laws that would lay the legal groundwork for the modern regime of food safety inspections.</em></p>
<p>Today, the inspection of food establishments is routine; Texas has codified what should be inspected, how frequently, and what constitutes an infraction of the health code. Less routine; the public’s access to both the overall results and the detailed violations. The short version, a restaurant is “scored” according to how many infractions an inspector finds– –if more than 30 points are deducted then an establishment must re-test within 10 days (it can also be closed). The longer version is, infractions carry different weights; up to 3 points for critical violations, and no, having animal matter in your facility does not incur instant closure. While many cities throughout Texas do provide online access to a table containing a food establishment’s name, address, and “raw” score, it lacks context. What infractions were found? Was this an outlier inspection?, does this establishment usually score well?, or poorly? Texas Law (25 Texas Admin. Code §228.253 (2015)) classifies this inspection report as a public document to be made available for disclosure to a person who requests it.</p>
<p>The basic nature of a GIS Professional should be that of a problem-solver; broke? fix it, missing? find it, and etc. Perhaps, the true artistry of our profession is presenting these solutions in a way that is visually pleasing and easy to “digest”. From my perspective, the web mapping application seemed simple enough– –show our citizenry data that we already collect. After working with Adobe Flex (now deprecated) web map applications for the last few years, I was looking forward to seeing how robust ESRI’s online Web AppBuilder (ArcGIS API for JavaScript, not deprecated) had become. The most difficult part of this project had to do with the logistics of fully fleshing out a large data set.</p>
<p>If the main problem with presenting food safety inspection information is context, how do we provide it effectively? Our chosen format has to be able to answer the specifics of the infraction, and the frequency of when infractions occur. My opinion was that we could preempt requests for disclosure of the inspection reports by making them freely available via the web application; this saves staff time spent on answering freedom of information requests and more closely follows the tenets of transparency in government. Next, we considered what an inspection report actually represents: a single moment of time within the average business day when an inspector visits an establishment. The results of the inspection are dependent on many factors, and ultimately, judging a food establishment for months at a time based off the events of a single half hour does not provide adequate context. A more temporally-holistic approach would be to consider multiple inspections. Satisfying these two conditions was straightforward; implementing this across the dataset was tedious.</p>
<p>Attaching documents to a feature layer is relatively simple, using a standard or advanced license, run the “Enable Attachments” geoprocessing tool. Congratulations!, you can now attach all sorts of files to the dataset that you selected. Pretty neat huh? For space considerations, I would recommend using PDF format (on average, it uses 300kb for two sheets) instead of image files. Our Health Inspectors were already scanning their paper inspection reports so there was no change in their workflow to help support the new dataset. Viewing attachments through your web map application is only a matter of checking a box: show feature attachments as links. This box resides just below the link: Configure Attributes; which is found in the Configure Pop-Up menu available in the web map (different from the web map application, it has the map viewer interface).</p>
<p><a href="../../img/posts/2017-12-04-application-still.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://feralcatcolonist.github.io/img/posts/2017-12-04-application-still.jpg" class="img-fluid" alt="an image of an ArcGIS Online Web App Builder application, it shows food establishments as points, the pop-up shows food safety scores and links to the original PDF files"></a></p>
<p>After the modifications to the data schema and the pop-up menu were out of the way, I had the ability to attach PDF documents to the feature layer itself and view them by clicking a link in the feature layer’s pop-up window. Another significant issue stemmed from the size of the data set that I created. Our City currently has 588 permitted, permanent food establishments; additionally, our current City Limit stands at approximately 68 square miles, with a build-out potential a little over 115 square miles. This dataset will grow. Each establishment is inspected 4 times a year, so this translates to roughly 2400 records that need to be updated this year. Let’s say on average it takes one minute to update the individual fields and attach the new inspection report– –you’re looking at 40 hours of work for this single data set for the entire year! Bump it up to two minutes a record and you’re at 80 hours, or two working weeks!</p>
<p>Getting around issues like data creep is one of the more striking challenges facing GIS professionals. There are three ways to approach it: automate, delegate, or hire more. Throwing man-hours at a problem is a solution, but generally not the most cost-effective. As a City, we are moving towards new software that will effectively digitize our records/permits from the point of submission in a SQL database; this will allow us to implement automation to remove the vast majority of maintenance hours for this dataset. The stumbling block here would be the financial cost of these new “city software” packages, along with the knowledge prerequisites in both SQL and Python.</p>
<p>Delegation of duties to other members in your organization, and organizing workflows, can reduce GIS work-hours. While everyone may not be comfortable spinning up ArcDesktop, most office workers can use Microsoft Excel (.xls) just fine; exporting a copy of the dataset as an excel file will allow other members of your organization to update the data. This removes time spent on editing individual fields by replacing that task with an Excel sheet (edited by others); join and then perform a field calculate. The last portion of the workflow is manually attaching documents. Name all scanned documents to include a unique key at some position within the filename (Ex. LocalCoffeeShop_3512.pdf or key6128_LocalTacoShoppe.pdf). With a unique key in place inside the filename, you have the ability to run the geoprocessing tool (using a standard or advanced license) Generate Attachment Match Table. This tool selects a data set, points to a folder on your machine/network, looks for the key field, and generates a table that pairs the files with the match key. Once the Generate Attachment Match Table tool has finished running, you can then run the geoprocessing tool (again, using a standard or advanced license) Add Attachments. This tool will select a data set, a key field, and using the Match Table (previously generated) will attach your documents en masse. Delegation, appropriate workflows, and the right geoprocessing tools, can reduce your maintenance time from a literal working week to the time it takes to run a join, field calculator, and two geoprocessing tools.</p>
<p>As an aside for the unique key: it may seem like a time saver to use the ObjectID or OID; it isn’t. If you ever need to rebuild the layer then your OIDs will all change– –this leaves you with a bunch of filenames that now have incorrect unique keys. The easiest way to create a simple sequential unique key is in Excel. Use the aforementioned Excel export of your dataset, type out a few records in the series, highlight them all, and drag down; Excel has a nice counter function that will auto-fill the numbers in the sequence. Asset management systems like Cartegraph and Cityworks also have unique key requirements, so it may be helpful to future-proof your data that you might be considering exploiting in another software system.</p>
<p>My personal journey was less… …neat. The files were already named without unique keys, the files had identical names (for the same establishment over time) and were only separated by month, and there was a backlog of reports dating back to October 2016. The extra tedious workflow that I followed looked like this: grab copies of all the inspections reports per month-folder, open PDF to view the inspection score, update the field attribute for that establishment, rename the PDF, and attach renamed PDF manually. Lather, rinse, repeat. This was repeated, 1244 times; 30 work hours (I did not average one record a minute). An hour editing session separated by a week or two isn’t that bad, but having a half-year’s worth of those lumped together really puts cumulative inefficiencies into stark perspective.</p>
<p>While my work is by no means one of life or death, the historical realities of the early 20th century that produced our current system of food safety and inspections definitely stemmed from such stakes. Today’s Texans are rightful recipients to the knowledge of what is in our food and in what conditions it is prepared. This web map application allows the citizens of our City and all those that dine within our borders to easily access that information.</p>



 ]]></description>
  <category>GIS</category>
  <category>Local Gov</category>
  <category>AGOL</category>
  <category>ArcGIS</category>
  <guid>https://feralcatcolonist.github.io/posts/2017-12-04_spatial-appetites/</guid>
  <pubDate>Mon, 04 Dec 2017 00:00:00 GMT</pubDate>
  <media:content url="https://feralcatcolonist.github.io/img/posts/2017-12-04-application-still.jpg" medium="image" type="image/jpeg"/>
</item>
</channel>
</rss>
