<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>procedual generation on Parsecs Reach</title>
    <link>https://parsecsreach.org/tags/procedual-generation/</link>
    <description>Recent content in procedual generation on Parsecs Reach</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <copyright>All rights reserved - 2022</copyright>
    <lastBuildDate>Fri, 25 Sep 2026 20:12:00 +0000</lastBuildDate><atom:link href="https://parsecsreach.org/tags/procedual-generation/index.xml" rel="self" type="application/rss+xml" />

    <item>
      <title>SVG Mountains</title>
      <link>https://parsecsreach.org/post/svg_mountains/</link>
      <pubDate>Fri, 25 Sep 2026 20:12:00 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/svg_mountains/</guid>
      <description>Recently I&amp;rsquo;ve been playing with drawing stuff in web pages. Little procedurally generated toys like these brambles or these mountain ranges I thought I&amp;rsquo;d write up a little guide on what I do when I build these and how I think about the problems.
Scaffolding To get started we need something to display stuff. Web browsers make this easy so lets make a web page with an SVG in it. Sure we could use python or rust or almost anything to generate SVGs but for this I&amp;rsquo;m going to use javascript and html.</description>
      <content:encoded><![CDATA[<p>Recently I&rsquo;ve been playing with drawing stuff in web pages. Little procedurally generated toys like <a href="https://parsecsreach.org/rewilding">these brambles</a> or <a href="https://parsecsreach.org/mountains">these mountain ranges</a> I thought I&rsquo;d write up a little guide on what I do when I build these and how I think about the problems.</p>
<p><img loading="lazy" src="/img/svg_mountains/mountains_screenshot.png" alt="svg mountains"  />
</p>
<h2 id="scaffolding">Scaffolding</h2>
<p>To get started we need something to display stuff. Web browsers make this easy so lets make a web page with an <a href="/post/svgs/">SVG</a> in it. Sure we could use python or <a href="/post/polygonical_and_esvg/">rust</a> or almost anything to generate SVGs but for this I&rsquo;m going to use javascript and html.</p>
<p>Create a file somewhere. Tradition and webserver ease means it should probably be called <code>index.html</code> unless you&rsquo;ve got more things in your web site that you want to wrap around this.</p>
<p>Inside this file we want to make the simplest svg we can get away with.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!DOCTYPE html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt;
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">title</span>&gt;Mountains&lt;/<span style="color:#f92672">title</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">body</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;margin:0; padding:0; background-color: black;&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">svg</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;image_root&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">version</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;1.1&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">xmlns</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;http://www.w3.org/2000/svg&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">xmlns:svg</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;http://www.w3.org/2000/svg&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">stroke</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;none&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">fill</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;none&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">fill-opacity</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;1&#34;</span>
</span></span><span style="display:flex;"><span>    &gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">svg</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;
</span></span></code></pre></div><p>This has the basic html we need. Set the doc type so browsers use modern html and javascript not the old magics. We give the page a title. Strictly speaking we don&rsquo;t need to do this but its nice. Then we have the body with an svg tag inside it.</p>
<p>We set some styles on the body, the first gets rid of the padding and margin that are there by default so the image we create can run all the way to the edge of the screen. The second sets the background colour. Yes we could use a style tag in the header but this is literally the only css we are going to need so it might as well go here.</p>
<p>On the svg tag we set the basic attributes to turn it into an svg, give it an id so we can find it easily, and set the stroke and fill styles. Both are set to none, so they will not display unless we set something else later. If we wanted we could set default line or fill styles here but each element of the thing I&rsquo;ll be making will have a different colour so it&rsquo;ll need updating later any way.</p>
<p>Now we have page, we want to see how it looks. Because this is a single html file we can probably get away with just opening the file by clicking on it or using <code>open</code>. However if in the future we want to include another file next to it then we will need some kind of server. Thankfully python provides a nice simple built in http server that you can spawn to host everything in a folder.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>python3 -m http.server <span style="color:#ae81ff">8000</span>
</span></span></code></pre></div><p>This will start a web server for you at http://localhost:8000 with the content of what ever folder you started it in. It shouldn&rsquo;t be used for real deployments but it works well enough for development.</p>
<h2 id="size-of-the-screen-and-view-ports">Size of the screen and view ports</h2>
<p>SVGs kind of try to figure out their size based on whats in them if you don&rsquo;t tell them how big they should be. Later we&rsquo;ll also need to know the size of the image so that we can lay things out as we want.</p>
<p>We&rsquo;ll set our image to the same size as the window, and record it in a global for later. Yes yes, globals are bad. You know why they are bad? Because of namespace pollution and multiple things accessing them. This thing&rsquo;s a single file with less than 200 lines of code. You know what? I think we can handle this. If this gets out of hand we can refactor it later.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#a6e22e">width</span> <span style="color:#f92672">=</span> window.<span style="color:#a6e22e">innerWidth</span> <span style="color:#f92672">||</span> document.<span style="color:#a6e22e">documentElement</span>.<span style="color:#a6e22e">clientWidth</span> <span style="color:#f92672">||</span> document.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">clientWidth</span>;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">height</span> <span style="color:#f92672">=</span> window.<span style="color:#a6e22e">innerHeight</span> <span style="color:#f92672">||</span> document.<span style="color:#a6e22e">documentElement</span>.<span style="color:#a6e22e">clientHeight</span> <span style="color:#f92672">||</span> document.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">clientHeight</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">svgEl</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#34;image_root&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">svgEl</span>.<span style="color:#a6e22e">width</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">width</span>;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">svgEl</span>.<span style="color:#a6e22e">height</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">height</span>;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">svgEl</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#34;viewBox&#34;</span>, <span style="color:#e6db74">&#34;0 0 &#34;</span><span style="color:#f92672">+</span><span style="color:#a6e22e">width</span><span style="color:#f92672">+</span><span style="color:#e6db74">&#34; &#34;</span><span style="color:#f92672">+</span><span style="color:#a6e22e">height</span>);
</span></span></code></pre></div><h2 id="svg-polygons">SVG Polygons</h2>
<p>Ok where were we? Trying to draw fun stuff? Right.</p>
<p>Lets draw a box. We can make it more complicated later. Once we can do this we can play around with algorithms and do fun stuff, but we need to be able to see what we are doing first.</p>
<p>Lets start with a simple box.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">box</span> <span style="color:#f92672">=</span> [[<span style="color:#ae81ff">0</span>,<span style="color:#ae81ff">0</span>], [<span style="color:#ae81ff">10</span>, <span style="color:#ae81ff">0</span>], [<span style="color:#ae81ff">10</span>, <span style="color:#ae81ff">10</span>], [<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">10</span>]]
</span></span></code></pre></div><p>An array of arrays representing a list of x, y points that draw a 10 unit square. SVGs can have almost any distance unit as their size, in our case we are drawing stuff on the screen so we probably want pixels eventually. So lets say these are pixels.</p>
<p>With SVGs we want a <code>path</code> element. Paths are more generic than lines, but they let us do all sorts of stuff with curves too. We don&rsquo;t care about that for now. We just need to draw straight lines between the points we defined above.</p>
<p>The path spec defines movement types. There are upper and lower case versions that depend on what reference frame they move on. Upper case ones move relative to the image origin, lower case move relative to the starting point. For what we need now we want <code>M</code> for move and <code>L</code> for line. So we end up with a
space separated list of movement commands, which are also space separated, yeah svg paths are a bit weird. Putting a Z on the end of the string will make it draw a line back to the first point. You can do this manually, but it makes the algorithm a little simpler to use the features it provides.</p>
<p>E.G</p>
<pre tabindex="0"><code>M0 0 L10 0 L10 10 L0 10 Z
</code></pre><p>We can build this using a map function from our original string</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">let</span> <span style="color:#a6e22e">pathStr</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">points</span>
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">map</span>(<span style="color:#66d9ef">function</span> (<span style="color:#a6e22e">point</span>, <span style="color:#a6e22e">index</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">index</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;M&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">point</span>[<span style="color:#ae81ff">0</span>] <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34; &#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">point</span>[<span style="color:#ae81ff">1</span>];
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;L&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">point</span>[<span style="color:#ae81ff">0</span>] <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34; &#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">point</span>[<span style="color:#ae81ff">1</span>];
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">join</span>(<span style="color:#e6db74">&#34; &#34;</span>) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;Z&#34;</span>;
</span></span></code></pre></div><p>And with the path string we can create the path element</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">let</span> <span style="color:#a6e22e">path</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createElementNS</span>(<span style="color:#a6e22e">svgNS</span>, <span style="color:#e6db74">&#34;path&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">path</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#34;id&#34;</span>, <span style="color:#e6db74">&#34;line-&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">id</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">path</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#34;stroke&#34;</span>, <span style="color:#e6db74">&#34;none&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">path</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#34;fill&#34;</span>, <span style="color:#e6db74">&#34;black&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">path</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#34;d&#34;</span>, <span style="color:#a6e22e">pathStr</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">svgEl</span>.<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">path</span>);
</span></span></code></pre></div><p>We should now have a black square on the svg when we load the page.</p>
<h2 id="random-numbers">Random numbers</h2>
<p>A slight detour, we are going to need some random numbers. We don&rsquo;t need super random cryptographic random numbers here. We&rsquo;re making art, not dealing with the safety of boeing whistle blowers. If we were doing this on paper, a bunch of dice would be fine. In javascript the <code>Math.random()</code> function is our dice. Except our dice give us a random number between 0 and 1 (but never actually 1)</p>
<pre tabindex="0"><code class="language-javascipt" data-lang="javascipt">&gt;&gt; Math.random()
0.2725780965518224
&gt;&gt; Math.random()
0.05027598093152419
&gt;&gt; Math.random()
0.6247010986076533
&gt;&gt; Math.random()
0.3988789395544631 
</code></pre><p>Numbers between 0 and 1 are not all that useful a lot of the time. So we&rsquo;re going to have to do some math to get them into the ranges we need.</p>
<p>To pick a random number between <code>a</code> and <code>b</code> where <code>b</code> is bigger, we can add and multiply</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#f92672">&gt;&gt;</span> (Math.<span style="color:#a6e22e">random</span>() <span style="color:#f92672">*</span> (<span style="color:#a6e22e">b</span><span style="color:#f92672">-</span><span style="color:#a6e22e">a</span>)) <span style="color:#f92672">+</span> <span style="color:#a6e22e">a</span>
</span></span></code></pre></div><p>Get the range, multiply by a random number to give us 0 to the gap between <code>a</code> and <code>b</code> then add on <code>a</code> to shift that range up to where we want.</p>
<p>But that&rsquo;s still a floating point number, it has many decimal points.</p>
<p>Bring in the <code>Math.floor(n)</code> function. This takes floating point number n and chops off the decimal points. Or rounds down.</p>
<p>This is really useful for picking an entry from an array for instance.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#f92672">&gt;&gt;&gt;</span> <span style="color:#a6e22e">myArray</span>[Math.<span style="color:#a6e22e">floor</span>(Math.<span style="color:#a6e22e">random</span>() <span style="color:#f92672">*</span> <span style="color:#a6e22e">myArray</span>.<span style="color:#a6e22e">length</span>)]
</span></span></code></pre></div><p>Note that <code>a</code> in the formula earlier is 0 here so it vanishes in a puff of simplification.</p>
<h2 id="making-wiggly-lines">Making wiggly lines</h2>
<p>So now we can make a wiggly line across the top of our square to make a mountain range. We can do this by stepping across the width of our page and deciding on a y coord based on a random number. But just picking a random number for the height at each step is going to make a line that is way way too spiky. We&rsquo;re trying to make mountains not a weird needle field.</p>
<p>Instead we can use a random number to decide if the next point should be above or below the previous point.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">let</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> [];
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">let</span> <span style="color:#a6e22e">prevY</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">height</span><span style="color:#f92672">/</span><span style="color:#ae81ff">2</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">x</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">x</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">width</span>; <span style="color:#a6e22e">x</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">y</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">prevY</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">r</span> <span style="color:#f92672">=</span> Math.<span style="color:#a6e22e">random</span>();
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">r</span> <span style="color:#f92672">&lt;</span> <span style="color:#ae81ff">0.5</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">y</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">prevY</span> <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">push</span>([<span style="color:#a6e22e">x</span>, <span style="color:#a6e22e">y</span>]);
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">prevY</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">y</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This will draw a wiggly line that will drift with the ups and downs of the random number generator rather than jumping long distances. You can make the choices more complex, for instance if its between 0.4 and 0.6 is stays flat, or maybe some ranges it jumps by 2 rather than one.</p>
<p>Now its a case of repeating this and making a gradient of them down the height of the screen.</p>
<h2 id="licensing">Licensing</h2>
<p>Giving stuff like this a license isn&rsquo;t going to do much to the llm scrapers, they really don&rsquo;t care and I don&rsquo;t have the funds to out lawyer them. However giving your project something like the <a href="https://git.disroot.org/bsdclown/filthy_human_hands">Filthy Human Hands</a> license or the <a href="https://firstdonoharm.dev/">Hippocratic license</a> is a good signal to other humans that you care that this is all a bit shit.</p>
<h2 id="hosting">Hosting</h2>
<p>This comes out with a single static html file, or a small number of them depending on how you structure it, this should be easy to host basically anywhere that allows you to upload your own html. Fun thing though one of the biggest llm advocates also provides free web hosting with github pages. So you can make an llm company deal with the ddos that is all the other llm companies. It&rsquo;s going to get scraped any way, might as well make them deal with it.</p>
<h2 id="fin">fin</h2>
<p>You can see the code for both projects listed at the beginning by looking at the source of the pages. Neither is particularly complex, and intentionally neither is minified or packed in any way. There is no build script or node dependencies. Raw html, svg, and javascript is unreasonably effective for making silly art things.</p>
<p>Go make silly art things.</p>
<p>And when you do tell me about it, so I can see more silly art things.</p>
<p>The world needs more silly art.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Rust workspaces and docker</title>
      <link>https://parsecsreach.org/post/rust_workspaces_docker/</link>
      <pubDate>Thu, 16 Apr 2026 21:02:00 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/rust_workspaces_docker/</guid>
      <description>Recently I&amp;rsquo;ve been working on a multi workspace rust project. It&amp;rsquo;s a client and server with some shared libraries and tools. I&amp;rsquo;ve managed to get it to build bits of its self in docker, with dependency caching, and a very minimal container image.
Rust workspaces are a way to have a monorepo with several sub projects. You can define dependency versions globally across all the sub projects to make things consistent, you can keep slowly changing builds cached, and not have to build some bits if you are not working on them.</description>
      <content:encoded><![CDATA[<p>Recently I&rsquo;ve been working on a multi workspace rust project. It&rsquo;s a client and server with some shared libraries and tools. I&rsquo;ve managed to get it to build bits of its self in docker, with dependency caching, and a very minimal container image.</p>
<p>Rust workspaces are a way to have a monorepo with several sub projects. You can define dependency versions globally across all the sub projects to make things consistent, you can keep slowly changing builds cached, and not have to build some bits if you are not working on them.</p>
<p>My project is a multiplayer game. The game or client side is using <a href="https://bevy.org/">bevy</a> which is a very large dependency and one of the largest libraries that I&rsquo;ve used with rust. The server is an <a href="https://actix.rs/">actix-web</a> and <a href="https://diesel.rs/">diesel</a> based project. Which are also quite large. I really don&rsquo;t want to be building bevy every time I change the web server, and vise versa. Also bevy is big enough my laptop with 16Gb of ram struggles to build it, some times it takes a couple of goes.</p>
<p>The server needs to run in docker, to make deployments easier. But I don&rsquo;t want to have to wait for the entire project to build every time I deploy it.</p>
<p>Workspaces have a layout like this in rust</p>
<pre tabindex="0"><code>|- Cargo.toml # top level workspace config.
|- Cargo.lock # global lock file of dependencies
|- data/  # a workspace folder (Shared library of data types to share between backend and front end)
| |- Cargo.toml # a workspace config
| |- src/
|   |- lib.rs 
|   |- ... # rust code here
|- game/ # The game client workspace
| |- Cargo.toml # bevy is defined here
| |- src/
|   |- main.rs
|   |- ... # rest of game here
|- server/
  |- Cargo.toml # server workspace config (defines actix-web and diesel)
  |- Diesel.toml
  |- src/
    |- main.rs
    |- ... rest of server code
</code></pre><p>Cargo defaults to building the entire thing when running <code>cargo build</code> or <code>cargo test</code>. To run a single application you use <code>cargo run --bin game</code> or <code>cargo run --bin server</code> There are plenty of guides on line on setting up workspaces, including in the rust book.</p>
<p>Building a basic docker container was pretty easy. First I started off with a two stage build</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Dockerfile" data-lang="Dockerfile"><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span><span style="color:#e6db74"> rust:1.94 AS build</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">WORKDIR</span><span style="color:#e6db74"> /parsecsreach</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> ./* .<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> cargo build --bin server --release<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">FROM</span><span style="color:#e6db74"> debian:bookworm-slim</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># install postgres system library</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> apt-get update <span style="color:#f92672">&amp;&amp;</span> apt-get install libpq-dev -y <span style="color:#f92672">&amp;&amp;</span> rm -rf /var/cache/apt/archives /var/lib/apt/lists/*<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># copy the build artifact from the build stage</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> --from<span style="color:#f92672">=</span>build /parsecsreach/target/release/server .<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># set the startup command to run your binary</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">CMD</span> [<span style="color:#e6db74">&#34;./server&#34;</span>]<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>A small wrinkle in there to get the postgres library into the bookworm-slim image but that&rsquo;s not too much of a problem. Fairly standard approach to installing a package and then nuking the archives and lists.</p>
<p>The issue with this is that every time you build the container you will rebuild all of the dependencies for the server. Which can take a few minutes on my laptop, which sucks from a iteration time point of view, no hot reload of the server as it needs compiling means we are stuck waiting for that every time we change a line of code. bleh</p>
<p>So what we want is to have the dependencies compiled in one layer while our code compiles in a later layer.</p>
<p>If we just copy the <code>Cargo.toml</code> files from each workspace over we will get an error saying that the workspace doesn&rsquo;t contain a main or lib file. For the library code we can just create an empty <code>lib.rs</code> in a <code>src</code> directory next to the <code>Cargo.toml</code> and it&rsquo;ll let us past with no real code.</p>
<p>For the binary workspaces we need a main function too. For that we can include a dummy rust main file somewhere and copy that in. Then we do a <code>cargo build --bin server --release</code> and we have all our dependencies compiled and cached, and we can copy our real code over the top and run the build again.</p>
<p>But &hellip;</p>
<p>If we do that and our dummy <code>lib.rs</code> and <code>main.rs</code> files are newer than the real ones (which if we touch the lib files to create them, is guaranteed) One of cargo or docker are &ldquo;smart&rdquo; enough to use that to think it doesn&rsquo;t need to copy over and rebuild our library and the binary for the server.</p>
<p>So we need to trick it into rebuilding the library and the binary.</p>
<p>The simplest I found was to remove the built artifacts for the library. I also tried changing the <code>Cargo.toml</code> files to point to a different main, which worked but seems a little more hacky.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Dockerfile" data-lang="Dockerfile"><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span><span style="color:#e6db74"> rust:1.94 AS build</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">WORKDIR</span><span style="color:#e6db74"> /parsecsreach</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># Copy cargo files</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> ./Cargo.toml ./Cargo.toml<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> ./Cargo.lock ./Cargo.lock<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># copy and create wireframe of the project so we can build only the dependencies.</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> ./data/Cargo.toml ./data/Cargo.toml<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> mkdir -p ./data/src <span style="color:#f92672">&amp;&amp;</span> touch ./data/src/lib.rs<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> ./server/Cargo.toml ./server/Cargo.toml<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> ./docker/template.main.rs ./server/src/dummy.rs<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> ./game/Cargo.toml ./game/Cargo.toml<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> ./docker/template.main.rs ./game/src/main.rs<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># this should only build the dependencies</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> sed -i <span style="color:#e6db74">&#39;s#src/main.rs#src/dummy.rs#&#39;</span> server/Cargo.toml<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> cargo build --bin server --release<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> sed -i <span style="color:#e6db74">&#39;s#src/dummy.rs#src/main.rs#&#39;</span> server/Cargo.toml<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># Clean out the library stub builds so they get built properly in a second.</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># These build files are newer than the ones in the source tree so cargo</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># doesn&#39;t think it needs rebuilding.</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> rm ./data/src/lib.rs <span style="color:#f92672">&amp;&amp;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>rm ./data/Cargo.toml <span style="color:#f92672">&amp;&amp;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>rm ./target/release/deps/libdata*<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># Copy workspaces that we need</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> ./data ./data<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> ./server ./server<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> cargo build --bin server --release<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># our final base</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">FROM</span><span style="color:#e6db74"> debian:bookworm-slim</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># install postgres system library</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> apt-get update <span style="color:#f92672">&amp;&amp;</span> apt-get install libpq-dev -y <span style="color:#f92672">&amp;&amp;</span> rm -rf /var/cache/apt/archives /var/lib/apt/lists/*<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># copy the build artifact from the build stage</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> --from<span style="color:#f92672">=</span>build /parsecsreach/target/release/server .<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># set the startup command to run your binary</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">CMD</span> [<span style="color:#e6db74">&#34;./server&#34;</span>]<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>Now the dependencies will only re-compile if we change the <code>Cargo.toml</code> files which is reasonable, and we have a nice tiny container image to actually run the thing.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Using python to configure rust Part 2</title>
      <link>https://parsecsreach.org/post/pyo3_config_part_2/</link>
      <pubDate>Wed, 26 Nov 2025 18:02:00 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/pyo3_config_part_2/</guid>
      <description>Continuing on from Part 1 If you&amp;rsquo;ve not read that you probably should before continuing here. Previously we setup a pyo3 project and ran some python code from rust which was able to create a rust class and return it to rust. (I think I can say rust a few more times in this paragraph. Rust rust rust)
This is the kind of python config we are aiming to be able to use inside our rust program:</description>
      <content:encoded><![CDATA[<p>Continuing on from <a href="/post/pyo3_config_part_1">Part 1</a> If you&rsquo;ve not read that you probably should before continuing here. Previously we setup a pyo3 project and ran some python code from rust which was able to create a rust class and return it to rust. (I think I can say rust a few more times in this paragraph. Rust rust rust)</p>
<p>This is the kind of python config we are aiming to be able to use inside our rust program:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#a6e22e">@configure</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">configA</span>():
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> Config(
</span></span><span style="display:flex;"><span>    name<span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;blah&#34;</span>
</span></span><span style="display:flex;"><span>  )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@configure</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">configB</span>(configA):
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> Config(
</span></span><span style="display:flex;"><span>    name<span style="color:#f92672">=</span> configA<span style="color:#f92672">.</span>name <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;_with_something_extra&#34;</span>
</span></span><span style="display:flex;"><span>  )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@configure</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">configC</span>(configA, configB):
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> Config(
</span></span><span style="display:flex;"><span>    name<span style="color:#f92672">=</span> configB<span style="color:#f92672">.</span>name <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;_and_even_more_&#34;</span> <span style="color:#f92672">+</span> configA<span style="color:#f92672">.</span>name,
</span></span><span style="display:flex;"><span>  )
</span></span></code></pre></div><p>Note the use of the <code>@configure</code> decorator to mark functions that do configuration stuff. That is the first thing we are going to do today.</p>
<h2 id="step-3-find-all-the-functions-with-the-decorator">Step 3: Find all the functions with the decorator</h2>
<p>Slight side bar: what the heck is a decorator?</p>
<p>In python a decorator is a bit of syntactic sugar for wrapping a function in another one. Defined by a function that takes the function its &ldquo;attached&rdquo; to as a parameter and returns a function. We could implement this <code>configure</code> decorator in python like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>LIST_OF_FUNCTIONS<span style="color:#f92672">=</span>[]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">configure</span>(inner_func):
</span></span><span style="display:flex;"><span>    LIST_OF_FUNCTIONS<span style="color:#f92672">.</span>append(inner_func)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> inner_func
</span></span></code></pre></div><p>We could then loop over <code>LIST_OF_FUNCTIONS</code> and we&rsquo;d have all our functions.</p>
<p>Of course we need that list in rust. Which isn&rsquo;t the easiest thing to create, what we just made in python is a global mutable variable. Which is a terrible idea as soon as more than one thread is involved. So rust doesn&rsquo;t let you do that.</p>
<p>Let me introduce you to the <a href="https://crates.io/crates/append-only-vec">append only vec</a></p>
<p>Once that&rsquo;s added to our <code>Cargo.toml</code> we can add this to our rust python module.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> append_only_vec::AppendOnlyVec;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">pub</span> <span style="color:#66d9ef">static</span> <span style="color:#66d9ef">CONFIGURE_FUNCTIONS</span>: <span style="color:#a6e22e">AppendOnlyVec</span><span style="color:#f92672">&lt;</span>Py<span style="color:#f92672">&lt;</span>PyAny<span style="color:#f92672">&gt;&gt;</span> <span style="color:#f92672">=</span> AppendOnlyVec::new();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[pyfunction]</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">pub</span> <span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">configure</span>(py: <span style="color:#a6e22e">Python</span>, inner: <span style="color:#a6e22e">Py</span><span style="color:#f92672">&lt;</span>PyAny<span style="color:#f92672">&gt;</span>) -&gt; <span style="color:#a6e22e">PyResult</span><span style="color:#f92672">&lt;</span>Py<span style="color:#f92672">&lt;</span>PyAny<span style="color:#f92672">&gt;&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">CONFIGURE_FUNCTIONS</span>.push(inner.clone_ref(py));
</span></span><span style="display:flex;"><span>    Ok(inner)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Please don&rsquo;t ask me how that works but the <code>AppendOnlyVec</code> is doing some magic that lets it add to its self with out being a modifiable version of its self. In the python code we can import the configure function along with the Config object and we can wrap our configure functions in the decorator as in the first code block.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> pyconfigmod <span style="color:#f92672">import</span> Config, configure
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@configure</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">configA</span>():
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> Config(
</span></span><span style="display:flex;"><span>    name<span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;blah&#34;</span>
</span></span><span style="display:flex;"><span>  )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@configure</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">configB</span>(configA):
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> Config(
</span></span><span style="display:flex;"><span>    name<span style="color:#f92672">=</span> configA<span style="color:#f92672">.</span>name <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;_with_something_extra&#34;</span>
</span></span><span style="display:flex;"><span>  )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@configure</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">configC</span>(configA, configB):
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> Config(
</span></span><span style="display:flex;"><span>    name<span style="color:#f92672">=</span> configB<span style="color:#f92672">.</span>name <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;_and_even_more_&#34;</span> <span style="color:#f92672">+</span> configA<span style="color:#f92672">.</span>name,
</span></span><span style="display:flex;"><span>  )
</span></span></code></pre></div><p>Now we have a list of functions we can loop over them. However we don&rsquo;t know what their parameters are or what order we need to execute them in. So &hellip;</p>
<h2 id="step-4-finding-the-arguments-for-the-functions">Step 4: Finding the arguments for the functions.</h2>
<p>At the moment we have a list of function references. We don&rsquo;t know what they are called and we don&rsquo;t know what arguments they take.</p>
<p>Python objects have a bunch of built in attributes commonly called magic methods, special methods, or dunder methods. They are the ones that have two underscores around their names. There is a helpful list of them in the <a href="https://docs.python.org/3/reference/datamodel.html#special-method-names">python documentation</a>.</p>
<p>The one we need here is one I&rsquo;ve not used in python before. <code>__code__</code> This contains information about the code that created the object. Details can be found, as usual, in the <a href="https://docs.python.org/3/reference/datamodel.html#code-objects">python docs</a>. For our needs we can use the <code>co_name</code> attribute to get the function name. I&rsquo;m not using the <code>co_qualname</code> because I&rsquo;ve not decided on a good mapping for characters that aren&rsquo;t allowed in parameter names. This does mean we need all <code>@configure</code> functions to have unique names, which kind of sucks currently. This can be a project for later.</p>
<p>The arguments to the function are a little more complex. Rather than separating out the arguments, they are part of the list of local variables. There is <code>co_argcount</code> which will give us how many there are, so we can pull them from the front of the list of local variables.</p>
<p>First lets get the function name</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">get_func_name</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span><span style="color:#f92672">&gt;</span>(_py: <span style="color:#a6e22e">Python</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span><span style="color:#f92672">&gt;</span>, func: <span style="color:#66d9ef">&amp;</span><span style="color:#a6e22e">Bound</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span>, PyAny<span style="color:#f92672">&gt;</span>) -&gt; <span style="color:#a6e22e">PyResult</span><span style="color:#f92672">&lt;</span>String<span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">!</span>func.is_callable() {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// TODO: figure out error handling
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        panic!(<span style="color:#e6db74">&#34;Object not callable.&#34;</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> code <span style="color:#f92672">=</span> func.getattr(<span style="color:#e6db74">&#34;__code__&#34;</span>)<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> name: String <span style="color:#f92672">=</span> code.getattr(<span style="color:#e6db74">&#34;co_name&#34;</span>)<span style="color:#f92672">?</span>.extract()<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>    Ok(name)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>I really need to sort out error handling at some point, but I&rsquo;m still in prototype mode so I&rsquo;m just going to panic if we get given something that isn&rsquo;t a function.</p>
<p>In the function we get the <code>__code__</code> attribute and then the <code>co_name</code> attribute from that, then we are done.</p>
<p>Now we can move on to the arguments.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">get_arg_names</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span><span style="color:#f92672">&gt;</span>(_py: <span style="color:#a6e22e">Python</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span><span style="color:#f92672">&gt;</span>, func: <span style="color:#66d9ef">&amp;</span><span style="color:#a6e22e">Bound</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span>, PyAny<span style="color:#f92672">&gt;</span>) -&gt; <span style="color:#a6e22e">PyResult</span><span style="color:#f92672">&lt;</span>Vec<span style="color:#f92672">&lt;</span>String<span style="color:#f92672">&gt;&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">!</span>func.is_callable() {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// TODO: figure out error handling
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        panic!(<span style="color:#e6db74">&#34;Object not callable.&#34;</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> code <span style="color:#f92672">=</span> func.getattr(<span style="color:#e6db74">&#34;__code__&#34;</span>)<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> arg_count: <span style="color:#66d9ef">i32</span> <span style="color:#f92672">=</span> code.getattr(<span style="color:#e6db74">&#34;co_argcount&#34;</span>)<span style="color:#f92672">?</span>.extract()<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> bound_var_names: <span style="color:#a6e22e">Bound</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span>, PyAny<span style="color:#f92672">&gt;</span> <span style="color:#f92672">=</span> code.getattr(<span style="color:#e6db74">&#34;co_varnames&#34;</span>)<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> bound_var_names.is_instance_of::<span style="color:#f92672">&lt;</span>PyTuple<span style="color:#f92672">&gt;</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> tuple <span style="color:#f92672">=</span> bound_var_names.cast::<span style="color:#f92672">&lt;</span>PyTuple<span style="color:#f92672">&gt;</span>()<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> result <span style="color:#f92672">=</span> Vec::new();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> i <span style="color:#66d9ef">in</span> <span style="color:#ae81ff">0</span><span style="color:#f92672">..</span>arg_count <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">usize</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> arg_name: String <span style="color:#f92672">=</span> tuple.get_item(i)<span style="color:#f92672">?</span>.extract()<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            result.push(arg_name);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        Ok(result)
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>        panic!(<span style="color:#e6db74">&#34;co_varnames wasn&#39;t a tuple&#34;</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This is a bit more complex &hellip; We grab the <code>__code__</code> attribute. We grab the <code>co_argcount</code> from it so we know how many arguments will be on the front of the <code>co_varnames</code> tuple. We then have to do some slightly nasty casting to get our <code>PyAny</code> into a <code>PyTuple</code> which we can then loop over the <code>arg_count</code> entries and pull out the argument names.</p>
<p>I feel ok panicking if the <code>co_varnames</code> attribute isn&rsquo;t a tuple given that its a built in python type and if that goes wrong the entire python environment is likely in flames so we won&rsquo;t be able to do much any way. Should probably check that the tuple actually has <code>arg_count</code> entries too but again, if the python environment we are using has gone that far wrong we have bigger issues.</p>
<p>Now that we have a way to get the name and arguments of a python function in rust land, we can build a <code>HashMap</code> of function name to arguments. In our main, after we have attached all the modules, we can do something like the following.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> dependencies: <span style="color:#a6e22e">HashMap</span><span style="color:#f92672">&lt;</span>String, Vec<span style="color:#f92672">&lt;</span>String<span style="color:#f92672">&gt;&gt;</span> <span style="color:#f92672">=</span> HashMap::new();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> functions: <span style="color:#a6e22e">HashMap</span><span style="color:#f92672">&lt;</span>String, Bound<span style="color:#f92672">&lt;</span>PyAny<span style="color:#f92672">&gt;&gt;</span> <span style="color:#f92672">=</span> HashMap::new();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> func <span style="color:#66d9ef">in</span> pyconfigmod::<span style="color:#66d9ef">CONFIGURE_FUNCTIONS</span>.iter() {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> bind <span style="color:#f92672">=</span> func.bind(py);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> bind.is_callable() {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">let</span> func_name <span style="color:#f92672">=</span> get_func_name(py, bind)<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> arg_names <span style="color:#f92672">=</span> get_arg_names(py, bind)<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                println!(<span style="color:#e6db74">&#34;func </span><span style="color:#e6db74">{}</span><span style="color:#e6db74"> args: </span><span style="color:#e6db74">{:?}</span><span style="color:#e6db74">&#34;</span>, func_name, arg_names);
</span></span><span style="display:flex;"><span>                functions.insert(func_name.clone(), bind.clone());
</span></span><span style="display:flex;"><span>                dependencies
</span></span><span style="display:flex;"><span>                    .entry(func_name)
</span></span><span style="display:flex;"><span>                    .or_insert_with(Vec::new)
</span></span><span style="display:flex;"><span>                    .append(<span style="color:#f92672">&amp;</span><span style="color:#66d9ef">mut</span> arg_names);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span></code></pre></div><p>Here we are going through the <code>CONFIGURE_FUNCTIONS</code> list we created with the decorator, which got run when we loaded the module. Binding the functions to our python instance, so we can pass around references nicely. Then if its callable grabbing its name and arguments and adding them to the dependencies map. I&rsquo;m also creating a name to function look up at the same time, we will need that later.</p>
<h1 id="part-5-sorting-our-functions">Part 5: Sorting our functions</h1>
<p>Now that we have our functions and know the names of their arguments we can sort them to work out the order that they need to run so we have the arguments required for each function before we call it. This set of functions is called a Directed Acyclic Graph (or DAG for short) This is a bunch of fancy words to mean they form a tree structure, with out any loops (or cycles).</p>
<p>The really important thing for us is the lack of cycles. If we have functions that depend on each other then we will not be able to run them because there is no way for us to start. E.G</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#a6e22e">@configure</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">configA</span>(configB):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> Config(name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Config A&#34;</span> <span style="color:#f92672">+</span> configA<span style="color:#f92672">.</span>name)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@configure</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">configB</span>(configA):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> Config(name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Config B&#34;</span> <span style="color:#f92672">+</span> configA<span style="color:#f92672">.</span>name)
</span></span></code></pre></div><p>To work out <code>configB</code> we need <code>configA</code> and to work out <code>configA</code> we need <code>configB</code> which is impossible for us to do. So we will be raising an error if this happens.</p>
<p>There are quite a few guides on doing a DAG sort online. I&rsquo;m going to include the code I wrote here and explain it, as its not too long.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">remove_args</span>(funcs: <span style="color:#66d9ef">&amp;</span><span style="color:#a6e22e">mut</span> HashMap<span style="color:#f92672">&lt;</span>String, Vec<span style="color:#f92672">&lt;</span>String<span style="color:#f92672">&gt;&gt;</span>, name: <span style="color:#66d9ef">&amp;</span><span style="color:#66d9ef">str</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> (_k, v) <span style="color:#66d9ef">in</span> funcs.iter_mut() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> Some(pos) <span style="color:#f92672">=</span> v.iter().position(<span style="color:#f92672">|</span>e<span style="color:#f92672">|</span> e <span style="color:#f92672">==</span> name) {
</span></span><span style="display:flex;"><span>            v.remove(pos);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">sort_functions</span>(funcs: <span style="color:#66d9ef">&amp;</span><span style="color:#a6e22e">HashMap</span><span style="color:#f92672">&lt;</span>String, Vec<span style="color:#f92672">&lt;</span>String<span style="color:#f92672">&gt;&gt;</span>) -&gt; Vec<span style="color:#f92672">&lt;</span>String<span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> working <span style="color:#f92672">=</span> funcs.clone();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> result <span style="color:#f92672">=</span> Vec::new();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">while</span> <span style="color:#f92672">!</span>working.is_empty() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> keys: Vec<span style="color:#f92672">&lt;</span>String<span style="color:#f92672">&gt;</span> <span style="color:#f92672">=</span> working.keys().cloned().collect();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> found_something <span style="color:#f92672">=</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> k <span style="color:#66d9ef">in</span> keys {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> args <span style="color:#f92672">=</span> working.get(<span style="color:#f92672">&amp;</span>k).unwrap();
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> args.is_empty() {
</span></span><span style="display:flex;"><span>                result.push(k.clone());
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                remove_args(<span style="color:#f92672">&amp;</span><span style="color:#66d9ef">mut</span> working, <span style="color:#f92672">&amp;</span>k);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                working.remove(<span style="color:#f92672">&amp;</span>k);
</span></span><span style="display:flex;"><span>                found_something <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// if we get here without breaking then we must have a loop or arguments that don&#39;t match functions.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">if</span> <span style="color:#f92672">!</span>found_something {
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// TODO: real errors here please
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            println!(<span style="color:#e6db74">&#34;working left with: </span><span style="color:#e6db74">{:?}</span><span style="color:#e6db74">&#34;</span>, working);
</span></span><span style="display:flex;"><span>            panic!(
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;Could not find any functions that have no parameters left. There must be a loop or parameters that don&#39;t match any functions.&#34;</span>
</span></span><span style="display:flex;"><span>            )
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> result;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>First we need a modifiable copy of our hashmap of functions and their arguments, we need to remove things from this as we work so its nicer to take a clone of it here.</p>
<p>Then we get on to the actual algorithm its self. This looks a bit more complex due to rust and the borrow checker, but the principle remains the same. Look through our working map of functions, find one that doesn&rsquo;t have any arguments. This means it has no dependencies, so add it to the result list. Then go through the working map and remove any arguments with the name of the function we have. We also need to remove the function from the working map or we will end up with an infinite loop.</p>
<p>Then we start again, and we keep going until the working map is empty. If we ever manage to loop over all the entries in the working map and don&rsquo;t find an entry that has no arguments, it means we have a loop somewhere, or we have argument names that don&rsquo;t match a function we know of. Either way that&rsquo;s an error and we need to tell the user about it, or in our case panic and stop the program.</p>
<p>One thing to note about this implementation is it is not entirely stable, by design. If there are two functions that take the same arguments, we don&rsquo;t care what order they run in. <code>working.keys()</code> returns the keys in a random order. If two configs don&rsquo;t have a defined order it should not matter which order they run in. If there is accidentally a dependency then that is a bug and they should have an extra parameter to make the ordering defined.</p>
<h1 id="part-6-running-the-configure-functions">Part 6: Running the configure functions</h1>
<p>Now that we have the information about all the configure functions and know the order they need to be executed in, we can start running them.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> sorted_functions <span style="color:#f92672">=</span> sort_functions(<span style="color:#f92672">&amp;</span>dependencies);
</span></span><span style="display:flex;"><span>        println!(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{:?}</span><span style="color:#e6db74">&#34;</span>, sorted_functions);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// execute the functions, keeping track of their results, looking up the arguments as needed.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> results_map: <span style="color:#a6e22e">HashMap</span><span style="color:#f92672">&lt;</span>String, Config<span style="color:#f92672">&gt;</span> <span style="color:#f92672">=</span> HashMap::new();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> func_name <span style="color:#66d9ef">in</span> sorted_functions {
</span></span><span style="display:flex;"><span>            println!(<span style="color:#e6db74">&#34;Executing </span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#34;</span>, <span style="color:#f92672">&amp;</span>func_name);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> args <span style="color:#f92672">=</span> dependencies.get(<span style="color:#f92672">&amp;</span>func_name).unwrap();
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> kwargs <span style="color:#f92672">=</span> create_kwargs(py, <span style="color:#f92672">&amp;</span>results_map, args)<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> result: <span style="color:#a6e22e">Config</span> <span style="color:#f92672">=</span> functions
</span></span><span style="display:flex;"><span>                .get(<span style="color:#f92672">&amp;</span>func_name)
</span></span><span style="display:flex;"><span>                .unwrap()
</span></span><span style="display:flex;"><span>                .call((), Some(<span style="color:#f92672">&amp;</span>kwargs))<span style="color:#f92672">?</span>
</span></span><span style="display:flex;"><span>                .extract()<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            results_map.insert(func_name.clone(), result);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        println!(<span style="color:#e6db74">&#34;results: </span><span style="color:#e6db74">{:?}</span><span style="color:#e6db74">&#34;</span>, results_map);
</span></span></code></pre></div><p>The code here loops over the list of function names that comes out of the sort function. Builds a set of <code>kwargs</code> (Key word args in python land) which is a <code>PyDict</code> that gets expanded into named arguments. Calls the function with those kwargs and finally stores the result for later.</p>
<p>When we create the kwargs we have to build a <code>PyDict</code>, I also convert our <code>Config</code> objects into <code>ReadOnlyConfig</code> objects so that later configure functions can not mess with the results of earlier functions. This is a near clone of the original <code>Config</code> object but it doesn&rsquo;t have the constructor function or the <code>set_all</code> argument on the <code>pyclass</code> macro. There is also a function to create one from a <code>Config</code> object.</p>
<p>The <code>create_kwargs</code> is as follows:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">create_kwargs</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span><span style="color:#f92672">&gt;</span>(
</span></span><span style="display:flex;"><span>    py: <span style="color:#a6e22e">Python</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span><span style="color:#f92672">&gt;</span>,
</span></span><span style="display:flex;"><span>    existing_results: <span style="color:#66d9ef">&amp;</span><span style="color:#a6e22e">HashMap</span><span style="color:#f92672">&lt;</span>String, Config<span style="color:#f92672">&gt;</span>,
</span></span><span style="display:flex;"><span>    args: <span style="color:#66d9ef">&amp;</span>Vec<span style="color:#f92672">&lt;</span>String<span style="color:#f92672">&gt;</span>,
</span></span><span style="display:flex;"><span>) -&gt; <span style="color:#a6e22e">PyResult</span><span style="color:#f92672">&lt;</span>Bound<span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span>, PyDict<span style="color:#f92672">&gt;&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> result <span style="color:#f92672">=</span> PyDict::new(py);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> arg <span style="color:#66d9ef">in</span> args {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> cr <span style="color:#f92672">=</span> existing_results.get(<span style="color:#f92672">&amp;</span>arg.to_string());
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> cr.is_none() {
</span></span><span style="display:flex;"><span>            panic!(
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;Couldn&#39;t find existing result for </span><span style="color:#e6db74">{}</span><span style="color:#e6db74">. This shouldn&#39;t be possible if the sort worked right&#34;</span>,
</span></span><span style="display:flex;"><span>                arg
</span></span><span style="display:flex;"><span>            );
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        result.set_item(arg, ReadOnlyConfig::from_config(cr.unwrap()))<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    Ok(result)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>As usual the error handling can be massively improved here, but something has gone horribly wrong if we don&rsquo;t have a result for a function we need. The sort would have had to have broken somehow.</p>
<p>Now we have a fully working configuration system, if MVP level of implementation, rather than production code. Lets run it on our config and see what happens.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>emily@diamondslab:~/Projects/breezeblock$ cargo run
</span></span><span style="display:flex;"><span>    Finished <span style="color:#e6db74">`</span>dev<span style="color:#e6db74">`</span> profile <span style="color:#f92672">[</span>unoptimized + debuginfo<span style="color:#f92672">]</span> target<span style="color:#f92672">(</span>s<span style="color:#f92672">)</span> in 0.02s
</span></span><span style="display:flex;"><span>     Running <span style="color:#e6db74">`</span>target/debug/breezeblock<span style="color:#e6db74">`</span>
</span></span><span style="display:flex;"><span>func configA args: <span style="color:#f92672">[]</span>
</span></span><span style="display:flex;"><span>func configB args: <span style="color:#f92672">[</span><span style="color:#e6db74">&#34;configA&#34;</span><span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>func configC args: <span style="color:#f92672">[</span><span style="color:#e6db74">&#34;configA&#34;</span>, <span style="color:#e6db74">&#34;configB&#34;</span><span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>dependencies: 
</span></span><span style="display:flex;"><span><span style="color:#f92672">{</span><span style="color:#e6db74">&#34;configC&#34;</span>: <span style="color:#f92672">[</span><span style="color:#e6db74">&#34;configA&#34;</span>, <span style="color:#e6db74">&#34;configB&#34;</span><span style="color:#f92672">]</span>, <span style="color:#e6db74">&#34;configA&#34;</span>: <span style="color:#f92672">[]</span>, <span style="color:#e6db74">&#34;configB&#34;</span>: <span style="color:#f92672">[</span><span style="color:#e6db74">&#34;configA&#34;</span><span style="color:#f92672">]}</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span><span style="color:#e6db74">&#34;configA&#34;</span>, <span style="color:#e6db74">&#34;configB&#34;</span>, <span style="color:#e6db74">&#34;configC&#34;</span><span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>Executing configA
</span></span><span style="display:flex;"><span>Executing configB
</span></span><span style="display:flex;"><span>Executing configC
</span></span><span style="display:flex;"><span>results: <span style="color:#f92672">{</span><span style="color:#e6db74">&#34;configB&#34;</span>: Config <span style="color:#f92672">{</span> config_class: <span style="color:#e6db74">&#34;&#34;</span>, repository: <span style="color:#e6db74">&#34;&#34;</span>, name: <span style="color:#e6db74">&#34;blah_with_something_extra&#34;</span>, features: <span style="color:#f92672">[]</span>, parameters: <span style="color:#f92672">{}</span> <span style="color:#f92672">}</span>, <span style="color:#e6db74">&#34;configC&#34;</span>: Config <span style="color:#f92672">{</span> config_class: <span style="color:#e6db74">&#34;&#34;</span>, repository: <span style="color:#e6db74">&#34;&#34;</span>, name: <span style="color:#e6db74">&#34;blah_with_something_extra_and_even_more_blah&#34;</span>, features: <span style="color:#f92672">[]</span>, parameters: <span style="color:#f92672">{}</span> <span style="color:#f92672">}</span>, <span style="color:#e6db74">&#34;configA&#34;</span>: Config <span style="color:#f92672">{</span> config_class: <span style="color:#e6db74">&#34;&#34;</span>, repository: <span style="color:#e6db74">&#34;&#34;</span>, name: <span style="color:#e6db74">&#34;blah&#34;</span>, features: <span style="color:#f92672">[]</span>, parameters: <span style="color:#f92672">{}</span> <span style="color:#f92672">}}</span>
</span></span></code></pre></div><p>In the results section at the end we can see it has done what we wanted.</p>
<p>There are many improvements that could be made here, but the basic principle works just fine. Now we can go and do something with our config objects.</p>
<p>I&rsquo;ve pushed the full code up to <a href="https://codeberg.org/emily_s/breezeblock">codeberg</a> so if you want to play around with it feel free. If you find this interesting or want to use it in something I&rsquo;d love to hear from you, drop me a message on mastodon @emily_<a href="mailto:s@mastodon.me.uk">s@mastodon.me.uk</a></p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Using python to configure rust Part 1</title>
      <link>https://parsecsreach.org/post/pyo3_config_part_1/</link>
      <pubDate>Mon, 24 Nov 2025 21:02:00 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/pyo3_config_part_1/</guid>
      <description>I&amp;rsquo;ve recently been playing with running python code from rust to use as a kind of configuration and extension system. The idea is that my program runs a bunch of python code that the user has defined and that gives it the configuration it needs. The user has the POWER OF THIS FULLY OPERATIONAL PROGRAMMING LANGUAGE cough cough, sorry voice box got stuck on imperial functionary there.
I&amp;rsquo;m doing this as a personal project and proof of concept so don&amp;rsquo;t expect bullet proof code here.</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve recently been playing with running python code from rust to use as a kind of configuration and extension system. The idea is that my program runs a bunch of python code that the user has defined and that gives it the configuration it needs. The user has the <em>POWER OF THIS FULLY OPERATIONAL PROGRAMMING LANGUAGE</em> cough cough, sorry voice box got stuck on imperial functionary there.</p>
<p>I&rsquo;m doing this as a personal project and proof of concept so don&rsquo;t expect bullet proof code here. If you want to do something similar then you will need to put some effort into hardening this. I&rsquo;ll also be pretty vague about what is being configured here as I don&rsquo;t think it matters right now. Think of something like helm charts or terraform config. Something that would usually be configured by a bunch of yaml files and maybe some templates. I want to know why they aren&rsquo;t configured with code, so I&rsquo;m experimenting to find the reasons.</p>
<p>This is going to be a series of posts and to avoid it being a book, I&rsquo;m going to assume you are familiar with rust and python. You know how to set up a project in both languages and are comfortable with their memory models, module systems, and standard libraries.</p>
<p>We can run python code in rust using the library <a href="https://pyo3.rs">pyo3</a>. To do this we needed to load the users code, find the right functions to call in the users code, and then run them. I could make them name their function something particular, but that doesn&rsquo;t give much power. It would be way nicer if they could add a decorator to their config functions and that allowed me to find them. What would also be cool is if we could chain them, so if the args to one function meant that it would call the function with that name first. E.g</p>
<pre tabindex="0"><code>@configure
def configA():
  return Config(
    name: &#34;blah&#34;
  )

@configure
def configB(configA):
  return Config(
    name: configA.name + &#34;_with_something_extra&#34;
  )

@configure
def configC(configA, configB):
  return Config(
    name: configB.name + &#34;_and_even_more_&#34; + configA.name,
  )
</code></pre><p>Where it is able to work out that it needs the results of <code>configA</code> and <code>configB</code> to be able to call <code>configC</code></p>
<p>There are a few steps here:</p>
<ol>
<li>Load all the python files in a folder into the python environment</li>
<li>Create a rust struct that python code can create for the config objects</li>
<li>Find all the functions with the <code>@configure</code> decorator on them</li>
<li>Work out what the arguments are for each function</li>
<li>Work out the order they should be executed in so we have the results needed as inputs to other functions</li>
<li>Execute the functions and save their results so we can pass them as parameters later.</li>
</ol>
<p>This is going to be a lot  so I&rsquo;ll split this into several posts.</p>
<h2 id="step-1-load-all-the-python-files-into-the-environment">Step 1: Load all the python files into the environment</h2>
<p>pyo3 works in rust by creating an environment to run in. Python doesn&rsquo;t have lifetimes and all the other fun and wonderful memory safety we have in rust. So all the python variables and memory gets put inside the pyo3 environment where it gets the lifetime of the environment. If you don&rsquo;t understand this, don&rsquo;t worry, its rust memory model stuff, you probably aren&rsquo;t the intended audience for this (sorry)</p>
<p>I&rsquo;m going to expect you&rsquo;ve got a rust environment set up and a python environment setup. I&rsquo;m on an old debian currently and have python 3.11.2 but I&rsquo;ve not seen anything that should break using different versions. I&rsquo;ve just not tested it.</p>
<p>Starting with a fresh <code>cargo new</code> project and adding <code>pyo3</code> to the dependencies.</p>
<pre tabindex="0"><code>pyo3 = {version=&#34;0.27&#34;, features=[&#34;auto-initialize&#34;]}
</code></pre><p>Now lets create the python environment inside our rust main.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> pyo3::prelude::<span style="color:#f92672">*</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() -&gt; <span style="color:#a6e22e">PyResult</span><span style="color:#f92672">&lt;</span>()<span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>    Python::attach(<span style="color:#f92672">|</span>py<span style="color:#f92672">|</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// do something in python here
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    });
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Inside that attached python block we can do things in python, see the pyo3 docs for <a href="https://pyo3.rs/v0.15.0/python_from_rust">examples</a> Here I&rsquo;m going to jump right into loading the users code from a folder somewhere.</p>
<p>pyo3 has a function to create a python module from a string. We can reasonably easily load a file into a string. One wrinkle is we need to use <code>CString</code> rather than the usual rust <code>String</code> because python operates in c land and we have to give it strings it will be able to comprehend.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">attach_module</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span><span style="color:#f92672">&gt;</span>(
</span></span><span style="display:flex;"><span>    py: <span style="color:#a6e22e">Python</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span><span style="color:#f92672">&gt;</span>,
</span></span><span style="display:flex;"><span>    root_dir: <span style="color:#66d9ef">&amp;</span><span style="color:#a6e22e">Path</span>,
</span></span><span style="display:flex;"><span>    input_path: <span style="color:#66d9ef">&amp;</span><span style="color:#a6e22e">Path</span>,
</span></span><span style="display:flex;"><span>) -&gt; <span style="color:#a6e22e">PyResult</span><span style="color:#f92672">&lt;</span>(CString, pyo3::Bound<span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span>, pyo3::types::PyModule<span style="color:#f92672">&gt;</span>)<span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> file_name <span style="color:#f92672">=</span> CString::new(input_path.file_name().unwrap().as_encoded_bytes())<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> module_name <span style="color:#f92672">=</span> CString::new(
</span></span><span style="display:flex;"><span>        input_path
</span></span><span style="display:flex;"><span>            .strip_prefix(root_dir)
</span></span><span style="display:flex;"><span>            .unwrap()
</span></span><span style="display:flex;"><span>            .to_str()
</span></span><span style="display:flex;"><span>            .unwrap()
</span></span><span style="display:flex;"><span>            .replace(<span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#e6db74">&#34;.&#34;</span>)
</span></span><span style="display:flex;"><span>            .strip_suffix(<span style="color:#e6db74">&#34;.py&#34;</span>)
</span></span><span style="display:flex;"><span>            .unwrap(),
</span></span><span style="display:flex;"><span>    )<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> content <span style="color:#f92672">=</span> CString::new(fs::read_to_string(input_path)<span style="color:#f92672">?</span>)<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> module <span style="color:#f92672">=</span> PyModule::from_code(py, <span style="color:#f92672">&amp;</span>content, <span style="color:#f92672">&amp;</span>file_name, <span style="color:#f92672">&amp;</span>module_name)<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    Ok((module_name, module))
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The first thing is the mess of a type signature. The lifetime parameter <code>'l</code> tells rust that the life time of the py parameter is going to be the life time of our results as well. We are going to be returning a <code>PyResult</code>, which is like a built in <code>Result</code> but it always returns a <code>PyErr</code> on errors. In the good case we are going to return a tuple of a CString (the module name) and a <code>pyo3::Bound</code> which acts like a rust <code>Rc</code> to the module with a lifetime.</p>
<p>Then there is a fair chunk of mess in there to convert the file path to a python module name. I&rsquo;m making the assumption that you are using an operating system that cleanly converts from <code>OsString</code> to <code>String</code>. I&rsquo;ve worked on windows, linux, and macs and I&rsquo;ve not seen that conversion fail so for this code I&rsquo;m ok with this assumption. It also assumes that its been given a string with <code>.py</code> extension, you&rsquo;ll see why I&rsquo;m ok with this in a minute.</p>
<p>Reading the file and converting it to a <code>CString</code> is a single line. (Its fun how some really complex things are easy in rust but some things are a pain like taking a file path and replacing all the / with spaces :) )</p>
<p>Once we&rsquo;ve got the text of the file, the filename and the module name we can call the <code>PyModule::from_code</code> to create the module. We have to hand it a reference to the python environment we are using so it knows where to create the module.</p>
<p>This handles a single file for us. Easy enough. Lets go one more level and load all the files in a directory, even if they reference each other.</p>
<p>To read all the files in a directory, including sub directories we can use the <a href="https://docs.rs/walkdir/latest/walkdir/">WalkDir</a> crate. This can give us an iterator over a directory and we can use the filter functions to find only the things we are interested in.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">attach_modules</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span><span style="color:#f92672">&gt;</span>(
</span></span><span style="display:flex;"><span>    py: <span style="color:#a6e22e">Python</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span><span style="color:#f92672">&gt;</span>,
</span></span><span style="display:flex;"><span>    input_path: <span style="color:#66d9ef">&amp;</span><span style="color:#66d9ef">str</span>,
</span></span><span style="display:flex;"><span>) -&gt; <span style="color:#a6e22e">PyResult</span><span style="color:#f92672">&lt;</span>HashMap<span style="color:#f92672">&lt;</span>String, pyo3::Bound<span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span>, pyo3::types::PyModule<span style="color:#f92672">&gt;&gt;&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> result <span style="color:#f92672">=</span> HashMap::new();
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> root_path <span style="color:#f92672">=</span> Path::new(input_path);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> py_str <span style="color:#f92672">=</span> OsStr::new(<span style="color:#e6db74">&#34;py&#34;</span>);
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> entry <span style="color:#66d9ef">in</span> WalkDir::new(input_path)
</span></span><span style="display:flex;"><span>        .into_iter()
</span></span><span style="display:flex;"><span>        .filter_map(<span style="color:#f92672">|</span>e<span style="color:#f92672">|</span> e.ok())
</span></span><span style="display:flex;"><span>        .filter(<span style="color:#f92672">|</span>e<span style="color:#f92672">|</span> e.metadata().unwrap().is_file())
</span></span><span style="display:flex;"><span>        .filter(<span style="color:#f92672">|</span>e<span style="color:#f92672">|</span> e.path().extension().unwrap_or(OsStr::new(<span style="color:#e6db74">&#34;&#34;</span>)) <span style="color:#f92672">==</span> py_str)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> path <span style="color:#f92672">=</span> entry.path();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> (name, module) <span style="color:#f92672">=</span> attach_module(py, <span style="color:#f92672">&amp;</span>root_path, path)<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>        result.insert(name.into_string()<span style="color:#f92672">?</span>, module);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    Ok(result)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This time our function takes a single path, where we are going to work from to find our python files. We create a HashMap (like a python dictionary) to use as the result then start using <code>WalkDir</code> First we filter out anything that the OS didn&rsquo;t let us read (permissions fun etc) then we filter for only files, then for paths which have the &ldquo;py&rdquo; extension. We then use the entries that gives us to call the <code>attach_module</code> function we created just now.</p>
<p>So now we&rsquo;ve been able to create all our modules. However they aren&rsquo;t actually available to each other. They are just Module objects, they are not really part of the python &ldquo;class path&rdquo; to mix my language concepts. Python manages its modules by keeping a dictionary in the built in <code>sys</code> module called <code>modules</code> when ever you try to import something in your code it looks in that dictionary to find it and errors if its not there.</p>
<p>We can do this in rust with a few modifications to the above function</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">attach_modules</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span><span style="color:#f92672">&gt;</span>(
</span></span><span style="display:flex;"><span>    py: <span style="color:#a6e22e">Python</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span><span style="color:#f92672">&gt;</span>,
</span></span><span style="display:flex;"><span>    input_path: <span style="color:#66d9ef">&amp;</span><span style="color:#66d9ef">str</span>,
</span></span><span style="display:flex;"><span>) -&gt; <span style="color:#a6e22e">PyResult</span><span style="color:#f92672">&lt;</span>HashMap<span style="color:#f92672">&lt;</span>String, pyo3::Bound<span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span>, pyo3::types::PyModule<span style="color:#f92672">&gt;&gt;&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> result <span style="color:#f92672">=</span> HashMap::new();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> sys <span style="color:#f92672">=</span> PyModule::import(py, <span style="color:#e6db74">&#34;sys&#34;</span>)<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> py_modules: <span style="color:#a6e22e">Bound</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">&#39;l</span>, PyDict<span style="color:#f92672">&gt;</span> <span style="color:#f92672">=</span> sys.getattr(<span style="color:#e6db74">&#34;modules&#34;</span>)<span style="color:#f92672">?</span>.cast_into()<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> root_path <span style="color:#f92672">=</span> Path::new(input_path);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> py_str <span style="color:#f92672">=</span> OsStr::new(<span style="color:#e6db74">&#34;py&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> entry <span style="color:#66d9ef">in</span> WalkDir::new(input_path)
</span></span><span style="display:flex;"><span>        .into_iter()
</span></span><span style="display:flex;"><span>        .filter_map(<span style="color:#f92672">|</span>e<span style="color:#f92672">|</span> e.ok())
</span></span><span style="display:flex;"><span>        .filter(<span style="color:#f92672">|</span>e<span style="color:#f92672">|</span> e.metadata().unwrap().is_file())
</span></span><span style="display:flex;"><span>        .filter(<span style="color:#f92672">|</span>e<span style="color:#f92672">|</span> e.path().extension().unwrap_or(OsStr::new(<span style="color:#e6db74">&#34;&#34;</span>)) <span style="color:#f92672">==</span> py_str)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> path <span style="color:#f92672">=</span> entry.path();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> (name, module) <span style="color:#f92672">=</span> attach_module(py, <span style="color:#f92672">&amp;</span>root_path, path)<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        py_modules.set_item(<span style="color:#f92672">&amp;</span>name, <span style="color:#f92672">&amp;</span>module)<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        result.insert(name.into_string()<span style="color:#f92672">?</span>, module);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    Ok(result)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>We import the &ldquo;sys&rdquo; module then get hold of the modules dictionary using the getattr method. We&rsquo;ll be using this a lot. It returns a <code>PyResult</code> that contains a reference to the python version of the value we asked for. This can be any attribute of the python object in question, in this case a global variable in the sys module. We can then call <code>py_modules.set_item</code> to insert the entry into the dictionary. Now all the modules should be able to find each other when we call them.</p>
<p>We&rsquo;ve successfully loaded all the python files in the directory. So on to Step 2.</p>
<h2 id="step-2-create-a-rust-config-struct-that-can-be-constructed-in-python">Step 2: Create a rust config struct that can be constructed in python.</h2>
<p>What we want here is a rust struct that we can do something like this to</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> somewhere <span style="color:#f92672">import</span> Config
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">construct</span>():
</span></span><span style="display:flex;"><span>    c <span style="color:#f92672">=</span> Config(name: <span style="color:#e6db74">&#34;foo&#34;</span>)
</span></span><span style="display:flex;"><span>    c<span style="color:#f92672">.</span>something <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;a value&#34;</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> c
</span></span></code></pre></div><p>Unfortunately we can&rsquo;t just import a rust type and have it work. That would be too much magic. There are some hoops we need to jump though.</p>
<p>The first is creating a new python module that we can import. I&rsquo;ve created a new rust file to put the config related stuff in. <code>config.rs</code> pyo3 has a macro to do this for us.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> pyo3::prelude::<span style="color:#f92672">*</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[pymodule]</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">pub</span> <span style="color:#66d9ef">mod</span> pyconfigmod {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Though again, like with us loading the python code this doesn&rsquo;t add it to the python world so we have do that separately. This needs a line before we attach to python, back in the <code>main.rs</code> file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">pub</span> <span style="color:#66d9ef">mod</span> config;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> config::pyconfigmod;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() -&gt; <span style="color:#a6e22e">PyResult</span><span style="color:#f92672">&lt;</span>()<span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>    pyo3::append_to_inittab!(pyconfigmod);
</span></span><span style="display:flex;"><span>    Python::attach(<span style="color:#f92672">|</span>py<span style="color:#f92672">|</span> {
</span></span></code></pre></div><p>Now we can create the class, it looks like a normal rust struct but with some extra macros, back in the <code>config.rs</code> we can add the struct.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> pyo3::prelude::<span style="color:#f92672">*</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[pymodule]</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">pub</span> <span style="color:#66d9ef">mod</span> pyconfigmod {
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">use</span> pyo3::prelude::<span style="color:#f92672">*</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[pyclass(set_all, get_all)]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[derive(Debug, Default, Clone)]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">pub</span> <span style="color:#66d9ef">struct</span> <span style="color:#a6e22e">Config</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">pub</span> name: String,
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Note: We need to import the prelude again inside the module. (Don&rsquo;t ask me why, this is a bit of rust I&rsquo;ve not figured out yet, and don&rsquo;t really need to understand right now. This works.)</p>
<p>By default pyo3 treats python classes as immutable. It makes it easier to reason about the thread safety of everything. However if you add the <code>set_all</code> and <code>get_all</code> parameters to the <code>pyclass</code> macro then you get generated getters and setters that take care of that synchronization mess for you. The final thing we need for this to work how we want is a constructor. Currently if we try to construct this thing in python it will tell us that it can&rsquo;t be constructed.</p>
<p>This also shows us how to add custom methods to our python class.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> pyo3::prelude::<span style="color:#f92672">*</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[pymodule]</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">pub</span> <span style="color:#66d9ef">mod</span> pyconfigmod {
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">use</span> pyo3::prelude::<span style="color:#f92672">*</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[pyclass(set_all, get_all)]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[derive(Debug, Default, Clone)]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">pub</span> <span style="color:#66d9ef">struct</span> <span style="color:#a6e22e">Config</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">pub</span> name: String,
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[pymethods]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">impl</span> Config {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">#[new]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">__new__</span>(name: String) -&gt; <span style="color:#a6e22e">Config</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> result <span style="color:#f92672">=</span> Config{name: <span style="color:#a6e22e">name</span>};
</span></span><span style="display:flex;"><span>                
</span></span><span style="display:flex;"><span>            result
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>While this is a very simple function we need to define it our selves.</p>
<p>As a result we are able to create a python file that looks like this</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> pyconfigmod <span style="color:#f92672">import</span> Config
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">something</span>():
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;configuring something&#34;</span>)
</span></span><span style="display:flex;"><span>    c <span style="color:#f92672">=</span> Config(<span style="color:#e6db74">&#34;my name&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Eventually do some thing more complex to create this here.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> c
</span></span></code></pre></div><p>Back in main we should run this for the grand payoff of seeing it print out something from our Config class. Back in our <code>main.rs</code> file we&rsquo;ve got something like this.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">pub</span> <span style="color:#66d9ef">mod</span> config;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> config::pyconfigmod;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// ... module loading code here ...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() -&gt; <span style="color:#a6e22e">PyResult</span><span style="color:#f92672">&lt;</span>()<span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>    pyo3::append_to_inittab!(pyconfigmod);
</span></span><span style="display:flex;"><span>    Python::attach(<span style="color:#f92672">|</span>py<span style="color:#f92672">|</span> {
</span></span><span style="display:flex;"><span>        modules <span style="color:#f92672">=</span> attach_modules(<span style="color:#e6db74">&#34;/home/emily/Projects/my_config&#34;</span>);
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>With the little python script in the previous code block in <code>/home/emily/Projects/my_config</code> we now have the module. So we can test this and see it working we will loop through each of the module and see if we can find an attribute called <code>something</code> and if its callable we will call it.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">pub</span> <span style="color:#66d9ef">mod</span> config;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> config::pyconfigmod;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// ... module loading code here ...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() -&gt; <span style="color:#a6e22e">PyResult</span><span style="color:#f92672">&lt;</span>()<span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>    pyo3::append_to_inittab!(pyconfigmod);
</span></span><span style="display:flex;"><span>    Python::attach(<span style="color:#f92672">|</span>py<span style="color:#f92672">|</span> {
</span></span><span style="display:flex;"><span>        modules <span style="color:#f92672">=</span> attach_modules(<span style="color:#e6db74">&#34;/home/emily/Projects/my_config&#34;</span>);
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> (module_name, module) <span style="color:#66d9ef">in</span> modules {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> module.hasattr(<span style="color:#e6db74">&#34;something&#34;</span>)<span style="color:#f92672">?</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">let</span> func_something <span style="color:#f92672">=</span> module.getattr(<span style="color:#e6db74">&#34;something&#34;</span>)<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>                println!(<span style="color:#e6db74">&#34;Found something in </span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#34;</span>, module_name);
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> func_something.is_callable() {
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">let</span> result: <span style="color:#a6e22e">Config</span> <span style="color:#f92672">=</span> func_something.call((), None)<span style="color:#f92672">?</span>.extract()<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>                    println!(<span style="color:#e6db74">&#34;Config received! Name: </span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#34;</span>, result.name);
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>If we run this with <code>cargo run</code> we should see something like</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>emily@diamondslab:~/Projects/breezeblock$ cargo run
</span></span><span style="display:flex;"><span>   Compiling breezeblock v0.1.0 <span style="color:#f92672">(</span>/home/emily/Projects/breezeblock<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    Finished <span style="color:#e6db74">`</span>dev<span style="color:#e6db74">`</span> profile <span style="color:#f92672">[</span>unoptimized + debuginfo<span style="color:#f92672">]</span> target<span style="color:#f92672">(</span>s<span style="color:#f92672">)</span> in 0.44s
</span></span><span style="display:flex;"><span>     Running <span style="color:#e6db74">`</span>target/debug/breezeblock<span style="color:#e6db74">`</span>
</span></span><span style="display:flex;"><span>Found something in something
</span></span><span style="display:flex;"><span>configuring something
</span></span><span style="display:flex;"><span>Config received! Name: my name
</span></span></code></pre></div><p>This is a good start. We&rsquo;ve got the ability to load a folder full of python files into rust. Create a rust class in python, and call functions in python from rust and get back a rust class. Next time we will work on finding python functions with a custom decorator and then working out what parameters those functions take.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>An introduction to Polygonical and ESVG</title>
      <link>https://parsecsreach.org/post/polygonical_and_esvg/</link>
      <pubDate>Sat, 07 Jun 2025 12:11:00 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/polygonical_and_esvg/</guid>
      <description>For the last ${scairy_number_of_years} years I&amp;rsquo;ve been working on some library tooling to create SVG files using rust. There are some libraries that existed already but they either didn&amp;rsquo;t work for what I needed to do or they had limitations. So I build two libraries of my own. Polygonical and ESVG. Polygonical focuses on 2d geometry operations. Points, lines, polygons and doing things to them. It does not concern its self with how they are shown, what format they are written to disk or anything like that.</description>
      <content:encoded><![CDATA[<p>For the last ${scairy_number_of_years} years I&rsquo;ve been working on some library tooling to create SVG files using rust. There are some libraries that existed already but they either didn&rsquo;t work for what I needed to do or they had limitations. So I build two libraries of my own. <a href="https://crates.io/crates/polygonical">Polygonical</a> and <a href="https://crates.io/crates/esvg">ESVG</a>. Polygonical focuses on 2d geometry operations. Points, lines, polygons and doing things to them. It does not concern its self with how they are shown, what format they are written to disk or anything like that. Its job is to make handling geometries easy. ESVG is a Document Object Model (DOM) based svg creation library, while it can read an svg it is mostly designed to build an svg in memory and write it out to disk or some where else.</p>
<p>Originally both of the libraries were inside another project that is used to create pages of shapes for my wife&rsquo;s etsy shop. However I realized they were useful for other things and wanted to be able to use them in different projects. So a great extraction and refactoring occurred and two new libraries were born.</p>
<p>Recently I&rsquo;ve been playing with procedual and random generation to create interesting patterns and realized this would make a good introduction tutorial to both libraries. This is what we are going to be aiming to create</p>
<p><img loading="lazy" src="/img/polygonical_and_esvg/square_grid_result.png" alt="Square grid result"  />
</p>
<h2 id="assumptions">Assumptions</h2>
<ul>
<li>You know some rust</li>
<li>You have a rust coding environment set up</li>
<li>You know a little about the SVG format. <a href="/post/svgs/">I wrote a very basic intro a few years ago</a></li>
</ul>
<h2 id="creating-a-blank-svg">Creating a blank SVG</h2>
<p>While we work we are going to want to see some stuff. This kind of thing I find easier to debug visually. So lets start by creating a blank svg document. A bit like a hello world.</p>
<p>First up create the project and add <code>esvg</code> and <code>polygonical</code> to the project.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cargo new svg_patterns
</span></span><span style="display:flex;"><span>cd svg_patterns
</span></span><span style="display:flex;"><span>cargo add esvg
</span></span><span style="display:flex;"><span>cargo add polygonical
</span></span></code></pre></div><p>Now we need to add the document creation and file handling. Open the <code>main.rs</code> file, it should look like this</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    println!(<span style="color:#e6db74">&#34;Hello, world!&#34;</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>There are two things we need to define before we can create a document. The first is the Dots Per Inch (DPI) we will be using. This defines the number of pixels per inch of space we will end up with in the final image. Common DPI values are 96 for things appearing on screen, 72 for some cutting machines, or around 300 for print work. For this I&rsquo;m going to set it to 96. The second thing we need is a definition of the &ldquo;paper&rdquo; we are going to be drawing on. I&rsquo;m not american so I&rsquo;m going to pick A4, but esvg has support for a <a href="https://docs.rs/esvg/0.5.0/esvg/page/struct.Page.html#method.A5">range of paper sizes</a> and <a href="https://docs.rs/esvg/0.5.0/esvg/page/struct.Page.html#method.build_page">custom ones</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> esvg::page::Page;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">DPI</span>: <span style="color:#66d9ef">i32</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">96</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> page <span style="color:#f92672">=</span> Page::A4(<span style="color:#66d9ef">DPI</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Now we can create our document.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> esvg::page::Page;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">DPI</span>: <span style="color:#66d9ef">i32</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">96</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> page <span style="color:#f92672">=</span> Page::A4(<span style="color:#66d9ef">DPI</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> document <span style="color:#f92672">=</span> esvg::create_document(<span style="color:#f92672">&amp;</span>page);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Finally we can write our document to a file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> esvg::page::Page;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">DPI</span>: <span style="color:#66d9ef">i32</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">96</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> page <span style="color:#f92672">=</span> Page::A4(<span style="color:#66d9ef">DPI</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> document <span style="color:#f92672">=</span> esvg::create_document(<span style="color:#f92672">&amp;</span>page);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    esvg::save(<span style="color:#e6db74">&#34;output_path.svg&#34;</span>, <span style="color:#f92672">&amp;</span>document).expect(<span style="color:#e6db74">&#34;Could not save SVG&#34;</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Obviously this could could do with some improvements, but it proves the point. When run it will create a svg file in the working directory called <code>output_path.svg</code> that will look like this internally</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-svg" data-lang="svg"><span style="display:flex;"><span><span style="color:#75715e">&lt;?xml version=&#34;1.0&#34; encoding=&#34;UTF-8&#34;?&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">&lt;!DOCTYPE svg PUBLIC &#34;-//W3C//DTD SVG 1.0//EN&#34; &#34;http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd&#34;&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;svg</span> <span style="color:#a6e22e">height=</span><span style="color:#e6db74">&#34;297.1270833333333mm&#34;</span> <span style="color:#a6e22e">viewBox=</span><span style="color:#e6db74">&#34;0, 0, 794, 1123&#34;</span> <span style="color:#a6e22e">width=</span><span style="color:#e6db74">&#34;210.07916666666668mm&#34;</span> <span style="color:#a6e22e">xmlns=</span><span style="color:#e6db74">&#34;http://www.w3.org/2000/svg&#34;</span> <span style="color:#a6e22e">xmlns:xlink=</span><span style="color:#e6db74">&#34;http://www.w3.org/1999/xlink&#34;</span> <span style="color:#f92672">/&gt;</span>
</span></span></code></pre></div><p>This doesn&rsquo;t do much. It sets up the doc type, and root <code>svg</code> tag, along with a couple of common namespaces.</p>
<p>It is usually worth setting some default style up on the root tag so that anything inside your image that doesn&rsquo;t have a style explicitly set will get something. Lets pull document creation out into a function.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> esvg::Element;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> esvg::page::Page;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">DPI</span>: <span style="color:#66d9ef">i32</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">96</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> page <span style="color:#f92672">=</span> Page::A4(<span style="color:#66d9ef">DPI</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> document <span style="color:#f92672">=</span> create_document(<span style="color:#f92672">&amp;</span>page);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    esvg::save(<span style="color:#e6db74">&#34;output_path.svg&#34;</span>, <span style="color:#f92672">&amp;</span>document).expect(<span style="color:#e6db74">&#34;Could not save SVG&#34;</span>);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">create_document</span>(page: <span style="color:#66d9ef">&amp;</span><span style="color:#a6e22e">Page</span>) -&gt; <span style="color:#a6e22e">Element</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> doc <span style="color:#f92672">=</span> esvg::create_document(page);
</span></span><span style="display:flex;"><span>    doc.set(<span style="color:#e6db74">&#34;stroke&#34;</span>, <span style="color:#e6db74">&#34;#8c8c8c&#34;</span>);
</span></span><span style="display:flex;"><span>    doc.set(<span style="color:#e6db74">&#34;fill&#34;</span>, <span style="color:#e6db74">&#34;none&#34;</span>);
</span></span><span style="display:flex;"><span>    doc.set(<span style="color:#e6db74">&#34;fill-opacity&#34;</span>, <span style="color:#e6db74">&#34;1&#34;</span>);
</span></span><span style="display:flex;"><span>    doc.set(<span style="color:#e6db74">&#34;stroke-opacity&#34;</span>, <span style="color:#e6db74">&#34;1&#34;</span>);
</span></span><span style="display:flex;"><span>    doc.set(<span style="color:#e6db74">&#34;stroke-width&#34;</span>, <span style="color:#e6db74">&#34;0.5mm&#34;</span>);
</span></span><span style="display:flex;"><span>    doc.set(<span style="color:#e6db74">&#34;stroke-linejoin&#34;</span>, <span style="color:#e6db74">&#34;miter&#34;</span>);
</span></span><span style="display:flex;"><span>    doc.set(<span style="color:#e6db74">&#34;stroke-dasharray&#34;</span>, <span style="color:#e6db74">&#34;none&#34;</span>);
</span></span><span style="display:flex;"><span>    doc.set(<span style="color:#e6db74">&#34;stroke-linecap&#34;</span>, <span style="color:#e6db74">&#34;square&#34;</span>);
</span></span><span style="display:flex;"><span>    doc.set(<span style="color:#e6db74">&#34;stroke-miterlimit&#34;</span>, <span style="color:#e6db74">&#34;10&#34;</span>);
</span></span><span style="display:flex;"><span>    doc.set(<span style="color:#e6db74">&#34;stroke-dashoffset&#34;</span>, <span style="color:#e6db74">&#34;0&#34;</span>);
</span></span><span style="display:flex;"><span>    doc.set(<span style="color:#e6db74">&#34;color-rendering&#34;</span>, <span style="color:#e6db74">&#34;auto&#34;</span>);
</span></span><span style="display:flex;"><span>    doc.set(<span style="color:#e6db74">&#34;color-interpolation&#34;</span>, <span style="color:#e6db74">&#34;auto&#34;</span>);
</span></span><span style="display:flex;"><span>    doc.set(<span style="color:#e6db74">&#34;text-rendering&#34;</span>, <span style="color:#e6db74">&#34;auto&#34;</span>);
</span></span><span style="display:flex;"><span>    doc.set(<span style="color:#e6db74">&#34;shape-rendering&#34;</span>, <span style="color:#e6db74">&#34;auto&#34;</span>);
</span></span><span style="display:flex;"><span>    doc.set(<span style="color:#e6db74">&#34;image-rendering&#34;</span>, <span style="color:#e6db74">&#34;auto&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    doc
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>I orgiginally cribbed most of these settings from what inkscape generates when you create a new image. The important ones are near the top. <code>stroke</code> controls the colour of the lines, <code>fill</code> controls the colour used inside shapes, <code>fill-opacity</code> and <code>stroke-opacity</code> control how transparant a shape is, they are a number between 0 for completely clear and 1 for completely solid. <code>stroke-width</code> controls how thick the paramiter line is. You can use any units here, if you don&rsquo;t provide units it will be in pixels. The rest are not so important and can be ignored mostly. If you need to look one up mozilla provide excellent <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/svg">svg reference docs</a>.</p>
<p>If we run the code now we&rsquo;ll get, but it&rsquo;ll still look like a blank page.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-svg" data-lang="svg"><span style="display:flex;"><span><span style="color:#75715e">&lt;?xml version=&#34;1.0&#34; encoding=&#34;UTF-8&#34;?&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">&lt;!DOCTYPE svg PUBLIC &#34;-//W3C//DTD SVG 1.0//EN&#34; &#34;http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd&#34;&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;svg</span> <span style="color:#a6e22e">color-interpolation=</span><span style="color:#e6db74">&#34;auto&#34;</span> <span style="color:#a6e22e">color-rendering=</span><span style="color:#e6db74">&#34;auto&#34;</span> <span style="color:#a6e22e">fill=</span><span style="color:#e6db74">&#34;none&#34;</span> <span style="color:#a6e22e">fill-opacity=</span><span style="color:#e6db74">&#34;1&#34;</span> <span style="color:#a6e22e">height=</span><span style="color:#e6db74">&#34;297.1270833333333mm&#34;</span> <span style="color:#a6e22e">image-rendering=</span><span style="color:#e6db74">&#34;auto&#34;</span> <span style="color:#a6e22e">shape-rendering=</span><span style="color:#e6db74">&#34;auto&#34;</span> <span style="color:#a6e22e">stroke=</span><span style="color:#e6db74">&#34;#8c8c8c&#34;</span> <span style="color:#a6e22e">stroke-dasharray=</span><span style="color:#e6db74">&#34;none&#34;</span> <span style="color:#a6e22e">stroke-dashoffset=</span><span style="color:#e6db74">&#34;0&#34;</span> <span style="color:#a6e22e">stroke-linecap=</span><span style="color:#e6db74">&#34;square&#34;</span> <span style="color:#a6e22e">stroke-linejoin=</span><span style="color:#e6db74">&#34;miter&#34;</span> <span style="color:#a6e22e">stroke-miterlimit=</span><span style="color:#e6db74">&#34;10&#34;</span> <span style="color:#a6e22e">stroke-opacity=</span><span style="color:#e6db74">&#34;1&#34;</span> <span style="color:#a6e22e">stroke-width=</span><span style="color:#e6db74">&#34;0.5mm&#34;</span> <span style="color:#a6e22e">text-rendering=</span><span style="color:#e6db74">&#34;auto&#34;</span> <span style="color:#a6e22e">viewBox=</span><span style="color:#e6db74">&#34;0, 0, 794, 1123&#34;</span> <span style="color:#a6e22e">width=</span><span style="color:#e6db74">&#34;210.07916666666668mm&#34;</span> <span style="color:#a6e22e">xmlns=</span><span style="color:#e6db74">&#34;http://www.w3.org/2000/svg&#34;</span> <span style="color:#a6e22e">xmlns:xlink=</span><span style="color:#e6db74">&#34;http://www.w3.org/1999/xlink&#34;</span> <span style="color:#f92672">/&gt;</span>
</span></span></code></pre></div><p>Lets draw something. The page we created at the start has helper methods to give the pixel locations of various points, top left corner, bottom right, etc. Lets draw a line across the page.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> page <span style="color:#f92672">=</span> Page::A4(<span style="color:#66d9ef">DPI</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> document <span style="color:#f92672">=</span> create_document(<span style="color:#f92672">&amp;</span>page);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> line <span style="color:#f92672">=</span> esvg::path::create(<span style="color:#f92672">&amp;</span>[page.top_left(), page.bottom_right()]);
</span></span><span style="display:flex;"><span>    document.add(<span style="color:#f92672">&amp;</span>line);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    esvg::save(<span style="color:#e6db74">&#34;output_path.svg&#34;</span>, <span style="color:#f92672">&amp;</span>document).expect(<span style="color:#e6db74">&#34;Could not save SVG&#34;</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>(note I&rsquo;ve only included main now to save space)</p>
<p>The path module in esvg provides helpers for creating path elements. In this case we pass in a slice that contains the coordinates we want to draw between. Note that the <code>top_left</code> method and its siblings all follow the margins applied to the page. They default to half an inch.  If you need to control the margins you can use the <code>A4_with_boarder</code> and similar functions.</p>
<p>One thing to note, when you add an element to a parent element it is cloned into the dom tree. Changes made to the element after it has been added will not be applied to the version that gets saved at the end. This is due to the DOM tree needing to take owner ship of the element to make sure it doesn&rsquo;t vanish when the original element variable goes out of scope.</p>
<p>If you open up the image you&rsquo;ll see something like this, where the line doesn&rsquo;t extend all the way to the edges.</p>
<p><img loading="lazy" src="/img/polygonical_and_esvg/line_result.png" alt="an svg with a line diagonally across the middle"  />
</p>
<p>Now we can start creating some patterns. We&rsquo;ll start with the square grid because its slightly simpler. First we need to decide what size of grid we want to create. The one I showed a screenshot of near the top used half centimeter spacing. ESVG provides a collection of <a href="https://docs.rs/esvg/0.5.0/esvg/convert/index.html">conversion functions</a> in the <code>convert</code> module. The useful one for us here is <code>parse_length</code>. This will take a string like <code>0.5cm</code> or <code>2.25in</code> and convert it into a number of pixels. With that number of pixels for the grid we can work out how many rows and columns we should have for the given page.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> page <span style="color:#f92672">=</span> Page::A4(<span style="color:#66d9ef">DPI</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> document <span style="color:#f92672">=</span> create_document(<span style="color:#f92672">&amp;</span>page);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> grid_spacing <span style="color:#f92672">=</span> convert::parse_length(<span style="color:#e6db74">&#34;0.5cm&#34;</span>, <span style="color:#66d9ef">DPI</span>).expect(<span style="color:#e6db74">&#34;could not parse length&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> num_rows <span style="color:#f92672">=</span> page.display_height_px() <span style="color:#f92672">/</span> grid_spacing;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> num_columns <span style="color:#f92672">=</span> page.display_width_px() <span style="color:#f92672">/</span> grid_spacing;
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    esvg::save(<span style="color:#e6db74">&#34;output_path.svg&#34;</span>, <span style="color:#f92672">&amp;</span>document).expect(<span style="color:#e6db74">&#34;Could not save SVG&#34;</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Now we can start actually using the polygonical library. We need to work out a grid of points across the page. We can use a pair of for loops to do the rows and columns. Then we need to create a <code>Point</code> on the page where we want to draw a corner.</p>
<p><code>Point</code> is the most basic type in polygonical. It represents a point in 2d space. The <code>page.top_left()</code> function we used earlier returns one, but we didn&rsquo;t need to worry about it then. They have an <code>x</code> and <code>y</code> value and can be translated or moved by other points, rotated around the origin.</p>
<p>Lets have a look at the code and then go through the important parts.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> page <span style="color:#f92672">=</span> Page::A4(<span style="color:#66d9ef">DPI</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> document <span style="color:#f92672">=</span> create_document(<span style="color:#f92672">&amp;</span>page);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> grid_spacing <span style="color:#f92672">=</span> convert::parse_length(<span style="color:#e6db74">&#34;0.5cm&#34;</span>, <span style="color:#66d9ef">DPI</span>).expect(<span style="color:#e6db74">&#34;could not parse length&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> num_rows <span style="color:#f92672">=</span> page.display_height_px() <span style="color:#f92672">/</span> grid_spacing;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> num_columns <span style="color:#f92672">=</span> page.display_width_px() <span style="color:#f92672">/</span> grid_spacing;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> y <span style="color:#66d9ef">in</span> <span style="color:#ae81ff">0</span><span style="color:#f92672">..</span>num_rows {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> x <span style="color:#66d9ef">in</span> <span style="color:#ae81ff">0</span><span style="color:#f92672">..</span>num_columns {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> point <span style="color:#f92672">=</span> page
</span></span><span style="display:flex;"><span>                .top_left()
</span></span><span style="display:flex;"><span>                .translate(<span style="color:#f92672">&amp;</span>Point::new(x <span style="color:#f92672">*</span> grid_spacing, y <span style="color:#f92672">*</span> grid_spacing));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> circle <span style="color:#f92672">=</span> esvg::shapes::circle(point, grid_spacing <span style="color:#f92672">/</span> <span style="color:#ae81ff">5</span>);
</span></span><span style="display:flex;"><span>            document.add(<span style="color:#f92672">&amp;</span>circle);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    esvg::save(<span style="color:#e6db74">&#34;output_path.svg&#34;</span>, <span style="color:#f92672">&amp;</span>document).expect(<span style="color:#e6db74">&#34;Could not save SVG&#34;</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>In the middle of that code we work out the point we want to draw a circle.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">let</span> point <span style="color:#f92672">=</span> page
</span></span><span style="display:flex;"><span>    .top_left()
</span></span><span style="display:flex;"><span>    .translate(<span style="color:#f92672">&amp;</span>Point::new(x <span style="color:#f92672">*</span> grid_spacing, y <span style="color:#f92672">*</span> grid_spacing));
</span></span></code></pre></div><p>This takes the point of the top left of the page, then moves it by a new point with its x and y coords set appropriately. We could keep a running variable and move it by <code>grid_spacing</code> each iteration but I find this slightly easier to understand personally.</p>
<p>After that we create a circle element and add it to the document, just like we did with our line previously. If we run this we should see something like this</p>
<p><img loading="lazy" src="/img/polygonical_and_esvg/circle_grid_result.png" alt="a grid of small circles at the points we defined in the code"  />
</p>
<p>Note that we have what looks like different margins on the right and bottom. This is because we are creating one less than the rows and columns calculated at the start. It&rsquo;ll become clear why later.</p>
<p>Let&rsquo;s draw some lines. We&rsquo;ve already seen how to do this so all we need is the end point of the line and we can create these reasonably easily. For each point we visit, if we draw a line to the right and a line downwards we will end up with a nice grid. While yes, we could draw lines all the way along the page and have a smaller SVG at the end, later we are going to want to be able to turn individual sections off so this will be easier if we do it like this.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> page <span style="color:#f92672">=</span> Page::A4(<span style="color:#66d9ef">DPI</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> document <span style="color:#f92672">=</span> create_document(<span style="color:#f92672">&amp;</span>page);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> grid_spacing <span style="color:#f92672">=</span> convert::parse_length(<span style="color:#e6db74">&#34;0.5cm&#34;</span>, <span style="color:#66d9ef">DPI</span>).expect(<span style="color:#e6db74">&#34;could not parse length&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> num_rows <span style="color:#f92672">=</span> page.display_height_px() <span style="color:#f92672">/</span> grid_spacing;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> num_columns <span style="color:#f92672">=</span> page.display_width_px() <span style="color:#f92672">/</span> grid_spacing;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> move_right <span style="color:#f92672">=</span> Point::new(grid_spacing, <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> move_down <span style="color:#f92672">=</span> Point::new(<span style="color:#ae81ff">0</span>, grid_spacing);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> y <span style="color:#66d9ef">in</span> <span style="color:#ae81ff">0</span><span style="color:#f92672">..</span>num_rows {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> x <span style="color:#66d9ef">in</span> <span style="color:#ae81ff">0</span><span style="color:#f92672">..</span>num_columns {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> point <span style="color:#f92672">=</span> page
</span></span><span style="display:flex;"><span>                .top_left()
</span></span><span style="display:flex;"><span>                .translate(<span style="color:#f92672">&amp;</span>Point::new(x <span style="color:#f92672">*</span> grid_spacing, y <span style="color:#f92672">*</span> grid_spacing));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> circle <span style="color:#f92672">=</span> esvg::shapes::circle(point, grid_spacing <span style="color:#f92672">/</span> <span style="color:#ae81ff">5</span>);
</span></span><span style="display:flex;"><span>            document.add(<span style="color:#f92672">&amp;</span>circle);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> right_point <span style="color:#f92672">=</span> point.translate(<span style="color:#f92672">&amp;</span>move_right);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> right_line <span style="color:#f92672">=</span> esvg::path::create(<span style="color:#f92672">&amp;</span>[point, right_point]);
</span></span><span style="display:flex;"><span>            document.add(<span style="color:#f92672">&amp;</span>right_line);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> down_point <span style="color:#f92672">=</span> point.translate(<span style="color:#f92672">&amp;</span>move_down);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> down_line <span style="color:#f92672">=</span> esvg::path::create(<span style="color:#f92672">&amp;</span>[point, down_point]);
</span></span><span style="display:flex;"><span>            document.add(<span style="color:#f92672">&amp;</span>down_line);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    esvg::save(<span style="color:#e6db74">&#34;output_path.svg&#34;</span>, <span style="color:#f92672">&amp;</span>document).expect(<span style="color:#e6db74">&#34;Could not save SVG&#34;</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>To save a bit on object churn I created a pair of points for moving right and moving down, so we could translate by the same points each time. There is nothing really stopping you creating new points in the loop, but I think this is a little clearer.</p>
<p>When you open this you&rsquo;ll notice that the right and bottom edges are not filled in correctly. Lets take care of that now. There are a few ways we could do this. Extra loops after the main loop, or adding a check to see if we are on the last row, and increasing the iteration counts by one. This is what I&rsquo;m going to go with.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> page <span style="color:#f92672">=</span> Page::A4(<span style="color:#66d9ef">DPI</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> document <span style="color:#f92672">=</span> create_document(<span style="color:#f92672">&amp;</span>page);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> grid_spacing <span style="color:#f92672">=</span> convert::parse_length(<span style="color:#e6db74">&#34;0.5cm&#34;</span>, <span style="color:#66d9ef">DPI</span>).expect(<span style="color:#e6db74">&#34;could not parse length&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> num_rows <span style="color:#f92672">=</span> page.display_height_px() <span style="color:#f92672">/</span> grid_spacing;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> num_columns <span style="color:#f92672">=</span> page.display_width_px() <span style="color:#f92672">/</span> grid_spacing;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> move_right <span style="color:#f92672">=</span> Point::new(grid_spacing, <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> move_down <span style="color:#f92672">=</span> Point::new(<span style="color:#ae81ff">0</span>, grid_spacing);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> y <span style="color:#66d9ef">in</span> <span style="color:#ae81ff">0</span><span style="color:#f92672">..=</span>num_rows {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> x <span style="color:#66d9ef">in</span> <span style="color:#ae81ff">0</span><span style="color:#f92672">..=</span>num_columns {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> point <span style="color:#f92672">=</span> page
</span></span><span style="display:flex;"><span>                .top_left()
</span></span><span style="display:flex;"><span>                .translate(<span style="color:#f92672">&amp;</span>Point::new(x <span style="color:#f92672">*</span> grid_spacing, y <span style="color:#f92672">*</span> grid_spacing));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> circle <span style="color:#f92672">=</span> esvg::shapes::circle(point, grid_spacing <span style="color:#f92672">/</span> <span style="color:#ae81ff">5</span>);
</span></span><span style="display:flex;"><span>            document.add(<span style="color:#f92672">&amp;</span>circle);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> x <span style="color:#f92672">&lt;</span> num_columns {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">let</span> right_point <span style="color:#f92672">=</span> point.translate(<span style="color:#f92672">&amp;</span>move_right);
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">let</span> right_line <span style="color:#f92672">=</span> esvg::path::create(<span style="color:#f92672">&amp;</span>[point, right_point]);
</span></span><span style="display:flex;"><span>                document.add(<span style="color:#f92672">&amp;</span>right_line);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> y <span style="color:#f92672">&lt;</span> num_rows {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">let</span> down_point <span style="color:#f92672">=</span> point.translate(<span style="color:#f92672">&amp;</span>move_down);
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">let</span> down_line <span style="color:#f92672">=</span> esvg::path::create(<span style="color:#f92672">&amp;</span>[point, down_point]);
</span></span><span style="display:flex;"><span>                document.add(<span style="color:#f92672">&amp;</span>down_line);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    esvg::save(<span style="color:#e6db74">&#34;output_path.svg&#34;</span>, <span style="color:#f92672">&amp;</span>document).expect(<span style="color:#e6db74">&#34;Could not save SVG&#34;</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Note that we switched from non inclusive ranges on the for loops to inclusive ranges. <code>..</code> to <code>..=</code></p>
<p>Now that we have built the grid we can make it randomly decide to skip some of the entries. For this we will use the <code>rand</code> library. However, it would be good if we could get the same result repeatedly so that we can test things easily. For that we need a random number generator that we can seed with a known value. Rust&rsquo;s rand crate doesn&rsquo;t have one, so we&rsquo;ll also need to bring in <code>rand_chacha</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cargo add rand
</span></span><span style="display:flex;"><span>cargo add rand_chacha
</span></span></code></pre></div><p>Now we need to create a rng and then use it to decide if we want to render our lines and circles.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> esvg::page::Page;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> esvg::{Element, convert};
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> polygonical::point::Point;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> rand::{Rng, SeedableRng <span style="color:#66d9ef">as</span> _};
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> rand_chacha::ChaCha8Rng;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">DPI</span>: <span style="color:#66d9ef">i32</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">96</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> page <span style="color:#f92672">=</span> Page::A4(<span style="color:#66d9ef">DPI</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> document <span style="color:#f92672">=</span> create_document(<span style="color:#f92672">&amp;</span>page);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> rng <span style="color:#f92672">=</span> ChaCha8Rng::seed_from_u64(<span style="color:#ae81ff">123456789</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> circle_chance <span style="color:#f92672">=</span> <span style="color:#ae81ff">0.5</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> line_chance <span style="color:#f92672">=</span> <span style="color:#ae81ff">0.75</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> grid_spacing <span style="color:#f92672">=</span> convert::parse_length(<span style="color:#e6db74">&#34;0.5cm&#34;</span>, <span style="color:#66d9ef">DPI</span>).expect(<span style="color:#e6db74">&#34;could not parse length&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> num_rows <span style="color:#f92672">=</span> page.display_height_px() <span style="color:#f92672">/</span> grid_spacing;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> num_columns <span style="color:#f92672">=</span> page.display_width_px() <span style="color:#f92672">/</span> grid_spacing;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> move_right <span style="color:#f92672">=</span> Point::new(grid_spacing, <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> move_down <span style="color:#f92672">=</span> Point::new(<span style="color:#ae81ff">0</span>, grid_spacing);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> y <span style="color:#66d9ef">in</span> <span style="color:#ae81ff">0</span><span style="color:#f92672">..=</span>num_rows {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> x <span style="color:#66d9ef">in</span> <span style="color:#ae81ff">0</span><span style="color:#f92672">..=</span>num_columns {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> point <span style="color:#f92672">=</span> page
</span></span><span style="display:flex;"><span>                .top_left()
</span></span><span style="display:flex;"><span>                .translate(<span style="color:#f92672">&amp;</span>Point::new(x <span style="color:#f92672">*</span> grid_spacing, y <span style="color:#f92672">*</span> grid_spacing));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> rng.random_bool(circle_chance) {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">let</span> circle <span style="color:#f92672">=</span> esvg::shapes::circle(point, grid_spacing <span style="color:#f92672">/</span> <span style="color:#ae81ff">5</span>);
</span></span><span style="display:flex;"><span>                document.add(<span style="color:#f92672">&amp;</span>circle);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> x <span style="color:#f92672">&lt;</span> num_columns <span style="color:#f92672">&amp;&amp;</span> rng.random_bool(line_chance) {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">let</span> right_point <span style="color:#f92672">=</span> point.translate(<span style="color:#f92672">&amp;</span>move_right);
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">let</span> right_line <span style="color:#f92672">=</span> esvg::path::create(<span style="color:#f92672">&amp;</span>[point, right_point]);
</span></span><span style="display:flex;"><span>                document.add(<span style="color:#f92672">&amp;</span>right_line);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> y <span style="color:#f92672">&lt;</span> num_rows <span style="color:#f92672">&amp;&amp;</span> rng.random_bool(line_chance) {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">let</span> down_point <span style="color:#f92672">=</span> point.translate(<span style="color:#f92672">&amp;</span>move_down);
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">let</span> down_line <span style="color:#f92672">=</span> esvg::path::create(<span style="color:#f92672">&amp;</span>[point, down_point]);
</span></span><span style="display:flex;"><span>                document.add(<span style="color:#f92672">&amp;</span>down_line);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    esvg::save(<span style="color:#e6db74">&#34;output_path.svg&#34;</span>, <span style="color:#f92672">&amp;</span>document).expect(<span style="color:#e6db74">&#34;Could not save SVG&#34;</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Things to note here: we had to import SeedableRng to get the trait that allows us to set the seed on the chacha8 random number generator. The rng has to be mutable, its internal state gets updated when it generates a random number so that the next one is different. I created two variables to hold the probability of lines and circles being created, this just makes it easier to alter them when playing around.</p>
<p>Now when we run this we should get something that looks like this</p>
<p><img loading="lazy" src="/img/polygonical_and_esvg/final_square_result.png" alt="final square grid that looks a bit like the one I posted back at the start"  />
</p>
<p>There is still plenty to do to this little program. Command line arguments, tidying up into functions, but the core is there. It produces the output we wanted. Hopefully its given you a small taste of whats possible when programmatically creating SVG files.</p>
<p>If you give this a try I&rsquo;d love to see the things you create. Drop me a message on <a href="https://mastodon.me.uk/@emily_s">mastodon</a></p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Creating a command line mastodon archive search tool in rust</title>
      <link>https://parsecsreach.org/post/rust_mastodon_search/</link>
      <pubDate>Wed, 15 Feb 2023 12:11:00 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/rust_mastodon_search/</guid>
      <description>Today lets step through creating a new command line tool in rust. Recently I wanted to find one of my old toots, but it was from several months ago and mastodon doesn&amp;rsquo;t have the ability to search for anything other than usernames and hashtags. I couldn&amp;rsquo;t remember if I&amp;rsquo;d put hash tags in the post I was after so I couldn&amp;rsquo;t use the built in thing. What I did have however is my backup archive.</description>
      <content:encoded><![CDATA[<p>Today lets step through creating a new command line tool in rust. Recently I wanted to find one of my old toots, but it was from several months ago and mastodon doesn&rsquo;t have the ability to search for anything other than usernames and hashtags. I couldn&rsquo;t remember if I&rsquo;d put hash tags in the post I was after so I couldn&rsquo;t use the built in thing. What I did have however is my backup archive. I&rsquo;d downloaded it earlier for unrelated reasons. Wouldn&rsquo;t it be good if I could run a search on that archive and get back the url for a toot I was looking for? Yep, lets build it. I&rsquo;m going to step through this in horrible detail as a kind of tutorial on creating a command line tool in rust. So here we go!</p>
<p>Ok, before we start there are some pre-requisites that I already  have setup and I&rsquo;m not going to talk about.</p>
<ul>
<li>I already have <a href="https://www.rust-lang.org/tools/install">rust installed</a></li>
<li>I have downloaded my archive (the option is in your settings under import/export, you may need to tap the hamburger button on mobile to find it)</li>
<li>I have a text editor and coding environment set up. In my case VS Code and the Rust analyser plugins.</li>
</ul>
<p>Now, lets have a terminal open and create our new tool.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cargo new magrep
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>     Created binary <span style="color:#f92672">(</span>application<span style="color:#f92672">)</span> <span style="color:#e6db74">`</span>magrep2<span style="color:#e6db74">`</span> package
</span></span></code></pre></div><p>This will create a new folder in the current directory called magrep (Mastodon Archive Grep) open that directory in your favourite text editor,</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cd magrep
</span></span><span style="display:flex;"><span>code .
</span></span></code></pre></div><p>There will be a <code>cargo.toml</code> a <code>.gitignore</code> and a <code>src/</code> directory with a <code>main.rs</code>. First thing we need is to run this. Ok, we don&rsquo;t strictly <em>need</em> to run this, but it is a good idea to make sure everything works.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cargo run
</span></span></code></pre></div><pre tabindex="0"><code>   Compiling magrep v0.1.0 (C:\Users\emily\git\magrep)
    Finished dev [unoptimized + debuginfo] target(s) in 1.86s
     Running `target\debug\magrep.exe`
Hello, world!
</code></pre><p>Hurrah it worked as expected. Now we need to have a quick think about what we want this tool to do and how it should work. We want to search the outbox.json file in the tar.gz archive file and look for any toots with a provided string in them. So we need to know where the archive file is, and what string to search for. When the thing runs it should open up the archive file, decode the outbox.json file, filter out anything thats not a toot (it includes likes and boosts too), and print out any that contain our search string.</p>
<p>Now we know that, the first thing the program is going to need to do is read the command line arguments and find out where the archive is and what we are searching for.</p>
<p>To do that we will use a library called Clap. This provides a bunch of command line parsing logic. We just need to add the following to the dependencies section of our <code>cargo.toml</code> file</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#a6e22e">clap</span> = { <span style="color:#a6e22e">version</span> = <span style="color:#e6db74">&#34;3.0&#34;</span>, <span style="color:#a6e22e">features</span> = [<span style="color:#e6db74">&#34;derive&#34;</span>] }
</span></span></code></pre></div><p>Then we can go to our <code>src/main.rs</code> file and start to write some code. We want an argument that is something like <code>-a &lt;path to archive file&gt;</code> so we need to create a new command and then add an argument to it. First add an import for the types we need,</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> clap::{Arg, Command};
</span></span></code></pre></div><p>Then add the following to your main function</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">let</span> matches <span style="color:#f92672">=</span> Command::new(<span style="color:#e6db74">&#34;magrep&#34;</span>)
</span></span><span style="display:flex;"><span>        .arg(
</span></span><span style="display:flex;"><span>            Arg::new(<span style="color:#e6db74">&#34;archive&#34;</span>)
</span></span><span style="display:flex;"><span>                .required(<span style="color:#66d9ef">true</span>)
</span></span><span style="display:flex;"><span>                .short(<span style="color:#e6db74">&#39;a&#39;</span>)
</span></span><span style="display:flex;"><span>                .long(<span style="color:#e6db74">&#34;archive&#34;</span>)
</span></span><span style="display:flex;"><span>                .help(<span style="color:#e6db74">&#34;file path for the archive to search&#34;</span>),
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        .get_matches();
</span></span></code></pre></div><p>This creates a command magrep and an argument with a short name of <code>-a</code> and a long name of <code>--archive</code> that is required. Now the next argument we need to create is the search term. Now it would be nice if we didn&rsquo;t have to specify that this was the search term, like when you use sed or grep you just type the search parameter. So lets create a positional argument.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">let</span> matches <span style="color:#f92672">=</span> Command::new(<span style="color:#e6db74">&#34;magrep&#34;</span>)
</span></span><span style="display:flex;"><span>        .arg(
</span></span><span style="display:flex;"><span>            Arg::new(<span style="color:#e6db74">&#34;archive&#34;</span>)
</span></span><span style="display:flex;"><span>                .required(<span style="color:#66d9ef">true</span>)
</span></span><span style="display:flex;"><span>                .short(<span style="color:#e6db74">&#39;a&#39;</span>)
</span></span><span style="display:flex;"><span>                .long(<span style="color:#e6db74">&#34;archive&#34;</span>)
</span></span><span style="display:flex;"><span>                .default_value(<span style="color:#e6db74">&#34;./archive.tar.gz&#34;</span>)
</span></span><span style="display:flex;"><span>                .help(<span style="color:#e6db74">&#34;file path for the archive to search&#34;</span>),
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        .arg(
</span></span><span style="display:flex;"><span>            Arg::with_name(<span style="color:#e6db74">&#34;query&#34;</span>)
</span></span><span style="display:flex;"><span>                .index(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>                .help(<span style="color:#e6db74">&#34;match string&#34;</span>)
</span></span><span style="display:flex;"><span>                .required(<span style="color:#66d9ef">true</span>),
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        .get_matches();
</span></span></code></pre></div><p>Now the matches variable will have our parsed command line arguments in it. We can pull them out into variables for us to use later like so</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> archive_path <span style="color:#f92672">=</span> matches.value_of(<span style="color:#e6db74">&#34;archive&#34;</span>).unwrap();
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> pattern <span style="color:#f92672">=</span> matches.value_of(<span style="color:#e6db74">&#34;query&#34;</span>).unwrap();
</span></span></code></pre></div><p>Lets do a quick test and print those out to make sure things are working&hellip;</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>    println!(<span style="color:#e6db74">&#34;path: </span><span style="color:#e6db74">{}</span><span style="color:#e6db74">, pattern: </span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#34;</span>, archive_path, pattern);
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cargo run -- -a something.gz <span style="color:#e6db74">&#34;some pattern&#34;</span>
</span></span></code></pre></div><p>This will take a bit longer than before as it will need to build clap and all of its dependencies too. You should see something like this</p>
<pre tabindex="0"><code>   Compiling hashbrown v0.12.3
   Compiling os_str_bytes v6.4.1
   Compiling once_cell v1.17.1
   Compiling bitflags v1.3.2
   Compiling strsim v0.10.0
   Compiling textwrap v0.16.0
   Compiling winapi v0.3.9
   Compiling clap_lex v0.2.4
   Compiling indexmap v1.9.2
   Compiling winapi-util v0.1.5
   Compiling atty v0.2.14
   Compiling termcolor v1.2.0
   Compiling clap v3.2.23
   Compiling magrep v0.1.0 (C:\Users\emily\git\magrep)
    Finished dev [unoptimized + debuginfo] target(s) in 9.00s
     Running `target\debug\magrep.exe -a something.gz &#34;some pattern&#34;`
path: something.gz, pattern: some pattern
</code></pre><p>As you can see at the end we got our arguments printed out nicely. Good. Now we can move on to trying to open the archive file. Now this is a tar.gz file, if you&rsquo;ve not encountered these before they are a compressed wrapper around a tar file, which is just some metadata and a bunch of files concatenated together. We&rsquo;ll use a couple of libraries to handle these. Add the following to your dependencies in your <code>cargo.toml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#a6e22e">flate2</span> = <span style="color:#e6db74">&#34;1.0.25&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">tar</span> = <span style="color:#e6db74">&#34;0.4.38&#34;</span>
</span></span></code></pre></div><p>We can import the bits we will shortly need,</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> std::fs::File;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> flate2::read::GzDecoder;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> tar::Archive;
</span></span></code></pre></div><p>Now we can open the file, wrap that file in a gz decoder, and wrap that in a tar archive decoder, like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> tar_gz <span style="color:#f92672">=</span> File::open(archive_path).unwrap();
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> tar <span style="color:#f92672">=</span> GzDecoder::new(tar_gz);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> archive <span style="color:#f92672">=</span> Archive::new(tar);
</span></span></code></pre></div><p>Lets do a test and print out the filenames in the archive, to make sure things are working as we expect.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> entry <span style="color:#66d9ef">in</span> archive.entries().unwrap() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> e <span style="color:#f92672">=</span> entry.unwrap();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> path_path <span style="color:#f92672">=</span> e.path().unwrap();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> path_string <span style="color:#f92672">=</span> path_path.to_str().unwrap();
</span></span><span style="display:flex;"><span>        println!(<span style="color:#e6db74">&#34;file: </span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#34;</span>, path_string);
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>Now if we run it we should get a list of filenames printed out&hellip;</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cargo run -- -a ../../Downloads/archive-20230214153535-daa49fde73e802447b521186ba95c8af.tar.gz <span style="color:#e6db74">&#34;some pattern&#34;</span>
</span></span></code></pre></div><pre tabindex="0"><code>   Compiling cfg-if v1.0.0
   Compiling adler v1.0.2
   Compiling windows_x86_64_msvc v0.42.1
   Compiling windows-targets v0.42.1
   Compiling crc32fast v1.3.2
   Compiling windows-sys v0.45.0
   Compiling miniz_oxide v0.6.2
   Compiling flate2 v1.0.25
   Compiling filetime v0.2.20
   Compiling tar v0.4.38
   Compiling magrep2 v0.1.0 (C:\Users\emily\git\magrep2)
    Finished dev [unoptimized + debuginfo] target(s) in 4.58s
     Running `target\debug\magrep2.exe -a ..\..\Downloads\archive-20230214153535-daa49fde73e802447b521186ba95c8af.tar.gz &#34;some pattern&#34;`
path: ..\..\Downloads\archive-20230214153535-daa49fde73e802447b521186ba95c8af.tar.gz, pattern: some pattern
file: media_attachments/files/000/857/528/original/f55902524814b2f2.jpeg
... cropped for brevity but there is a load more media attachments in my archive...
file: media_attachments/files/109/834/160/834/865/079/original/ce8fe16924be4b35.jpg
file: outbox.json
file: likes.json
file: bookmarks.json
file: avatar.jpg
file: actor.json
</code></pre><p>There is the outbox.json file we want to have a look inside of, so lets see whats inside. The entry object we have above acts like a reader so we can read it to a string like any other file. Lets modify our loop from the previous entry to something like this</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> entry <span style="color:#66d9ef">in</span> archive.entries().unwrap() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> e <span style="color:#f92672">=</span> entry.unwrap();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> path_path <span style="color:#f92672">=</span> e.path().unwrap();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> path_string <span style="color:#f92672">=</span> path_path.to_str().unwrap();
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> path_string <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;outbox.json&#34;</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> buffer <span style="color:#f92672">=</span> String::new();
</span></span><span style="display:flex;"><span>            e.read_to_string(<span style="color:#f92672">&amp;</span><span style="color:#66d9ef">mut</span> buffer).unwrap();
</span></span><span style="display:flex;"><span>            println!(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{buffer}</span><span style="color:#e6db74">&#34;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>Note we need to make the entry e mutable here as the read_to_string method changes the position of of the read pointer in the &ldquo;file&rdquo;</p>
<p>I won&rsquo;t show the output here, suffice to say its a single giant line of json. So our next task is going to be decoding it. For this we need yet another dependency. This time <code>serde_json</code>. <code>serde</code> is a collection of libraries for encoding and decoding rust structs. The json library deals with json encoding and decoding.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#a6e22e">serde_json</span> = <span style="color:#e6db74">&#34;1.0.93&#34;</span>
</span></span></code></pre></div><p>At this point we could define a set of structs that could represent the json object structure inside the <code>outbox.json</code> file. However that seems like a lot of work considering we really only need to pick out a couple of fields, the content, and the url. Helpfully serde_json has a built in Value type that can represent any arbitrary json object. So we can use that and save a bunch of typing and code.</p>
<p>First as usual import the library</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> serde_json::Value;
</span></span></code></pre></div><p>Then we can modify our loop:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> outbox : Option<span style="color:#f92672">&lt;</span>Value<span style="color:#f92672">&gt;</span> <span style="color:#f92672">=</span> None;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> entry <span style="color:#66d9ef">in</span> archive.entries().unwrap() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> e <span style="color:#f92672">=</span> entry.unwrap();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> path_path <span style="color:#f92672">=</span> e.path().unwrap();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> path_string <span style="color:#f92672">=</span> path_path.to_str().unwrap();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> path_string <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;outbox.json&#34;</span> <span style="color:#f92672">&amp;&amp;</span> outbox.is_none() {
</span></span><span style="display:flex;"><span>            outbox <span style="color:#f92672">=</span> Some(serde_json::from_reader(e).unwrap());
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> outbox.is_none() {
</span></span><span style="display:flex;"><span>        println!(<span style="color:#e6db74">&#34;no outbox file in archive !invalid!&#34;</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>Small change to our loop here to decode the json object. I&rsquo;m making a style choice here to store the decoded outbox json object for later. I don&rsquo;t like to have things too deeply nested, they get difficult to understand and this allows us to handle the next bit outside the loop. We do have to take care of the case where we get a tar.gz file that doesn&rsquo;t contain an <code>outbox.json</code> file so we store it in a option and check that the option is not none after the loop.</p>
<p>So now we have the decoded json object out of the tar ball with out writing anything temporary to disk, which is nice, so now we can start searching for toots that match our <code>pattern</code>. Lets take a look at this json object we have and see what we can do with it. We could print it out but we already know that its huge and going to roll off the end of our shell if we try and print it all out. So lets have a look at what keys are in the top level and see if that helps.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> key <span style="color:#66d9ef">in</span> outbox.unwrap().as_object().unwrap().keys() {
</span></span><span style="display:flex;"><span>        println!(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#34;</span>, key);
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>Much unwrapping later&hellip; so we take our <code>Option&lt;Value&gt;</code> and unwrap it to a <code>Value</code> we know this is safe because we checked is_none() above and exited if it was. We then convert the Value to an object representation with the <code>as_object</code> call. This returns an <code>Option&lt;&amp;Map&lt;String, Value&gt;&gt;</code>, we kind of know this will work and is safe to unwrap, but if it wasn&rsquo;t it would be ok to panic here as we are just playing around to figure out our data structure. Then we can get an iterator of the keys and loop over them.</p>
<p>If we run this we should get something like:</p>
<pre tabindex="0"><code>   Compiling ryu v1.0.12
   Compiling serde v1.0.152
   Compiling itoa v1.0.5
   Compiling serde_json v1.0.93
   Compiling magrep2 v0.1.0 (C:\Users\emily\git\magrep2)
    Finished dev [unoptimized + debuginfo] target(s) in 7.86s
     Running `target\debug\magrep2.exe -a ..\..\Downloads\archive-20230214153535-daa49fde73e802447b521186ba95c8af.tar.gz &#34;some pattern&#34;`
path: ..\..\Downloads\archive-20230214153535-daa49fde73e802447b521186ba95c8af.tar.gz, pattern: some pattern
@context
id
orderedItems
totalItems
type
</code></pre><p>That <code>orderedItems</code> key looks promising. Lets take a closer look. Hopefully its an array (if the world makes any sense &hellip; we should probably check) lets try it and see what explodes</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>println!(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#34;</span>, outbox.unwrap()[<span style="color:#e6db74">&#34;orderedItems&#34;</span>].as_array().unwrap()[<span style="color:#ae81ff">0</span>]);
</span></span></code></pre></div><p>This should output something like this, my first ever toot! (Hay nothing exploded!)</p>
<pre tabindex="0"><code>{&#34;actor&#34;:&#34;https://tech.lgbt/users/Emily_S&#34;,&#34;cc&#34;:[&#34;https://tech.lgbt/users/Emily_S/followers&#34;],&#34;id&#34;:&#34;https://tech.lgbt/users/Emily_S/statuses/103363936687819074/activity&#34;,&#34;object&#34;:{&#34;atomUri&#34;:&#34;https://tech.lgbt/users/Emily_S/statuses/103363936687819074&#34;,&#34;attachment&#34;:[],&#34;attributedTo&#34;:&#34;https://tech.lgbt/users/Emily_S&#34;,&#34;cc&#34;:[&#34;https://tech.lgbt/users/Emily_S/followers&#34;],&#34;content&#34;:&#34;&lt;p&gt;Going to try this mastodon thing again. &lt;/p&gt;&lt;p&gt;Hello everyone. I&amp;#39;m Emily. A trans lady from the UK. (Yes we made a mess of things lately didn&amp;#39;t we) software engineer, space nerd, maker of things and maps, parent and wife.&lt;/p&gt;&lt;p&gt;I&amp;#39;m interested in lots of data processing stuff, I work in a geographic field so you&amp;#39;ll probably get rants about projection systems, buggy code, interesting code tricks, and other general stuff.&lt;/p&gt;&#34;,&#34;contentMap&#34;:{&#34;en&#34;:&#34;&lt;p&gt;Going to try this mastodon thing again. &lt;/p&gt;&lt;p&gt;Hello everyone. I&amp;#39;m Emily. A trans lady from the UK. (Yes we made a mess of things lately didn&amp;#39;t we) software engineer, space nerd, maker of things and maps, parent and wife.&lt;/p&gt;&lt;p&gt;I&amp;#39;m interested in lots of data processing stuff, I work in a geographic field so you&amp;#39;ll probably get rants about projection systems, buggy code, interesting code tricks, and other general stuff.&lt;/p&gt;&#34;},&#34;conversation&#34;:&#34;tag:tech.lgbt,2019-12-24:objectId=3818591:objectType=Conversation&#34;,&#34;id&#34;:&#34;https://tech.lgbt/users/Emily_S/statuses/103363936687819074&#34;,&#34;inReplyTo&#34;:null,&#34;inReplyToAtomUri&#34;:null,&#34;published&#34;:&#34;2019-12-24T17:28:26Z&#34;,&#34;replies&#34;:{&#34;first&#34;:{&#34;items&#34;:[],&#34;next&#34;:&#34;https://tech.lgbt/users/Emily_S/statuses/103363936687819074/replies?only_other_accounts=true&amp;page=true&#34;,&#34;partOf&#34;:&#34;https://tech.lgbt/users/Emily_S/statuses/103363936687819074/replies&#34;,&#34;type&#34;:&#34;CollectionPage&#34;},&#34;id&#34;:&#34;https://tech.lgbt/users/Emily_S/statuses/103363936687819074/replies&#34;,&#34;type&#34;:&#34;Collection&#34;},&#34;sensitive&#34;:false,&#34;summary&#34;:null,&#34;tag&#34;:[],&#34;to&#34;:[&#34;https://www.w3.org/ns/activitystreams#Public&#34;],&#34;type&#34;:&#34;Note&#34;,&#34;url&#34;:&#34;https://tech.lgbt/@Emily_S/103363936687819074&#34;},&#34;published&#34;:&#34;2019-12-24T17:28:26Z&#34;,&#34;signature&#34;:{&#34;created&#34;:&#34;2023-02-14T15:32:30Z&#34;,&#34;creator&#34;:&#34;https://tech.lgbt/users/Emily_S#main-key&#34;,&#34;signatureValue&#34;:&#34;sdTf+YO4x3og1ApJWIOZ5sEsRq49cCJe35ZJdUzOX+jfna6nMzaWUeAgv4Maz0Iig3SJ7ODNqrONghfQPUdfp6ObwUKwWsSmIfA3mvGZsgElfpkkKFLzqCHGWnW+dOLN6CebFKUCMf5YpWwPvFrxAX4bzs/EAnjCnMK43VWFS/srQo7BEBYnv9qkNeBfR2UNz0xkjQ0HP2YeGOXavNlCovNyN6zSyenFA66h3vNehX3un4szMCdM/u0LodU3pKSonyw8kTBaBpkzkqFHmlTQ5jX+0ZyA/+szFE6FkdTR8zG16bjuvxXoBezyq1rKRxjvGPhWehThYk8YpLtsVl28Yg==&#34;,&#34;type&#34;:&#34;RsaSignature2017&#34;},&#34;to&#34;:[&#34;https://www.w3.org/ns/activitystreams#Public&#34;],&#34;type&#34;:&#34;Create&#34;}
</code></pre><p>Ok, so we have the content of our toot twice for internationalisation reasons by the look of it. (I guess something got extended at some point in the history of activity pub and now we have both content and contentMap fields) the other one that looks promising is the <code>atomUri</code> which looks a lot like a toot url. It is, we can put that into a browser and it brings up the toot in question.</p>
<p>So lets finish this thing and be done.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> item <span style="color:#66d9ef">in</span> outbox.unwrap()[<span style="color:#e6db74">&#34;orderedItems&#34;</span>].as_array().unwrap() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> item[<span style="color:#e6db74">&#34;object&#34;</span>][<span style="color:#e6db74">&#34;content&#34;</span>].as_str().unwrap().contains(pattern) {
</span></span><span style="display:flex;"><span>            println!(<span style="color:#e6db74">&#34;*************&#34;</span>);
</span></span><span style="display:flex;"><span>            println!(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{}</span><span style="color:#e6db74"> : </span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#34;</span>,item[<span style="color:#e6db74">&#34;object&#34;</span>][<span style="color:#e6db74">&#34;atomUri&#34;</span>], item[<span style="color:#e6db74">&#34;object&#34;</span>][<span style="color:#e6db74">&#34;content&#34;</span>]);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>So we loop over all the items in the orderedItems list and then get the content as a string and see if it contains the pattern. If so print out its atomUri and the content so we can see it.</p>
<p>Unfortunately if we run this we&rsquo;ll get a panic</p>
<pre tabindex="0"><code>   Compiling magrep v0.1.0 (C:\Users\emily\git\magrep)
    Finished dev [unoptimized + debuginfo] target(s) in 1.47s
     Running `target\debug\magrep.exe -a ..\..\Downloads\archive-20230214153535-daa49fde73e802447b521186ba95c8af.tar.gz &#34;some pattern&#34;`
path: ..\..\Downloads\archive-20230214153535-daa49fde73e802447b521186ba95c8af.tar.gz, pattern: some pattern
thread &#39;main&#39; panicked at &#39;called `Option::unwrap()` on a `None` value&#39;, src\main.rs:52:37
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn&#39;t exit successfully: `target\debug\magrep.exe -a ..\..\Downloads\archive-20230214153535-daa49fde73e802447b521186ba95c8af.tar.gz &#34;some pattern&#34;` (exit code: 101)
</code></pre><p>What this is telling us is we tried to call unwrap on a option that was None, on line 52 of main.rs. Which is the if statement in the above block. The only unwrap in there is the as_str() call so we got a content that wasn&rsquo;t a string? More likely we got an item that didn&rsquo;t have a content field at all. Lets print some things out and see what happens.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> item <span style="color:#66d9ef">in</span> outbox.unwrap()[<span style="color:#e6db74">&#34;orderedItems&#34;</span>].as_array().unwrap() {
</span></span><span style="display:flex;"><span>        println!(<span style="color:#e6db74">&#34;%%%%%%%%%%%%%%%%&#34;</span>);
</span></span><span style="display:flex;"><span>        println!(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#34;</span>, item);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> item[<span style="color:#e6db74">&#34;object&#34;</span>][<span style="color:#e6db74">&#34;content&#34;</span>].as_str().unwrap().contains(pattern) {
</span></span><span style="display:flex;"><span>            println!(<span style="color:#e6db74">&#34;*************&#34;</span>);
</span></span><span style="display:flex;"><span>            println!(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{}</span><span style="color:#e6db74"> : </span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#34;</span>, item[<span style="color:#e6db74">&#34;object&#34;</span>][<span style="color:#e6db74">&#34;atomUri&#34;</span>], item[<span style="color:#e6db74">&#34;object&#34;</span>][<span style="color:#e6db74">&#34;content&#34;</span>]);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>I like to include separators so its easy to see whats going on, hence the &lsquo;%&rsquo; symbols. With luck when we run this now we should be able to see the cause of the panic.</p>
<pre tabindex="0"><code>...
%%%%%%%%%%%%%%%%
{&#34;@context&#34;:&#34;https://www.w3.org/ns/activitystreams&#34;,&#34;actor&#34;:&#34;https://tech.lgbt/users/Emily_S&#34;,&#34;cc&#34;:[&#34;https://tiny.tilde.website/users/selfsame&#34;,&#34;https://tech.lgbt/users/Emily_S/followers&#34;],&#34;id&#34;:&#34;https://tech.lgbt/users/Emily_S/statuses/103370741914550623/activity&#34;,&#34;object&#34;:&#34;https://tiny.tilde.website/users/selfsame/statuses/101789047613657946&#34;,&#34;published&#34;:&#34;2019-12-25T22:19:06Z&#34;,&#34;to&#34;:[&#34;https://www.w3.org/ns/activitystreams#Public&#34;],&#34;type&#34;:&#34;Announce&#34;}
thread &#39;main&#39; panicked at &#39;called `Option::unwrap()` on a `None` value&#39;, src\main.rs:54:47
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn&#39;t exit successfully: `target\debug\magrep.exe -a ..\..\Downloads\archive-20230214153535-daa49fde73e802447b521186ba95c8af.tar.gz &#34;some pattern&#34;` (exit code: 101)
</code></pre><p>Ah, its something that doesn&rsquo;t have a content field inside the object. In fact the object is just a string. I think this is a boost, which is not something we want to search so we can ignore them. I remember now I did mention this way way back at the start of this post, oops. That <code>type</code> field looks like a good candidate for picking out what we need. Lets add a filter on that being &ldquo;Create&rdquo;</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> item <span style="color:#66d9ef">in</span> outbox.unwrap()[<span style="color:#e6db74">&#34;orderedItems&#34;</span>].as_array().unwrap() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> item[<span style="color:#e6db74">&#34;type&#34;</span>].as_str().unwrap() <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;Create&#34;</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> item[<span style="color:#e6db74">&#34;object&#34;</span>][<span style="color:#e6db74">&#34;content&#34;</span>].as_str().unwrap().contains(pattern) {
</span></span><span style="display:flex;"><span>                println!(<span style="color:#e6db74">&#34;*************&#34;</span>);
</span></span><span style="display:flex;"><span>                println!(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{}</span><span style="color:#e6db74"> : </span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#34;</span>, item[<span style="color:#e6db74">&#34;object&#34;</span>][<span style="color:#e6db74">&#34;atomUri&#34;</span>], item[<span style="color:#e6db74">&#34;object&#34;</span>][<span style="color:#e6db74">&#34;content&#34;</span>]);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>And if we run it now we get&hellip;</p>
<pre tabindex="0"><code>   Compiling magrep v0.1.0 (C:\Users\emily\git\magrep)
    Finished dev [unoptimized + debuginfo] target(s) in 1.20s
     Running `target\debug\magrep.exe -a ..\..\Downloads\archive-20230214153535-daa49fde73e802447b521186ba95c8af.tar.gz &#34;some pattern&#34;`
path: ..\..\Downloads\archive-20230214153535-daa49fde73e802447b521186ba95c8af.tar.gz, pattern: some pattern
</code></pre><p>Ah I&rsquo;ve never tooted &ldquo;some pattern&rdquo; lets try something else&hellip;</p>
<pre tabindex="0"><code>PS C:\Users\emily\git\magrep&gt; cargo run -- -a ..\..\Downloads\archive-20230214153535-daa49fde73e802447b521186ba95c8af.tar.gz &#34;fish&#34;
   Compiling magrep v0.1.0 (C:\Users\emily\git\magrep)
    Finished dev [unoptimized + debuginfo] target(s) in 1.66s
     Running `target\debug\magrep.exe -a ..\..\Downloads\archive-20230214153535-daa49fde73e802447b521186ba95c8af.tar.gz fish`
path: ..\..\Downloads\archive-20230214153535-daa49fde73e802447b521186ba95c8af.tar.gz, pattern: fish
*************
&#34;https://tech.lgbt/users/Emily_S/statuses/109295945016110296&#34; : &#34;&lt;p&gt;So let&amp;#39;s dig in to some usecases: &lt;/p&gt;&lt;p&gt;Say your country has a system to license vessels to fish in your waters, but also can&amp;#39;t really afford a big navy. With ais data you can track vessels in your waters and deal with any that look like they are fishing.&lt;/p&gt;&lt;p&gt;Boats that are fishing move very differently to a cargo ship trying to get to the next port, so even if it lies about being a fishing vessel you can tell.&lt;/p&gt;&#34;
*************
&#34;https://tech.lgbt/users/Emily_S/statuses/109295960022052842&#34; : &#34;&lt;p&gt;What about verification that the fish you are selling in your super market is caught legally? This is a good one because it&amp;#39;s just making sure vessels are doing what they say they are, so they will want to keep their transmitters on to prove they are good and can sell their fish.&lt;/p&gt;&lt;p&gt;You can also keep track of your cargo containers and know where the shipments are out side of what the shipping company says. That snafu in the suez was fun to watch.&lt;/p&gt;&#34;
*************
&#34;https://tech.lgbt/users/Emily_S/statuses/109451801563109639&#34; : &#34;&lt;p&gt;&lt;span class=\&#34;h-card\&#34;&gt;&lt;a href=\&#34;https://scicomm.xyz/@delibrarian\&#34; class=\&#34;u-url mention\&#34;&gt;@&lt;span&gt;delibrarian&lt;/span&gt;&lt;/a&gt;&lt;/span&gt; good question. For the purposes of story telling I&amp;#39;d go with nothing, but it acting like a fish bowl where they can&amp;#39;t come here and we can&amp;#39;t leave would be interesting.&lt;/p&gt;&#34;
</code></pre><p>Apparently I&rsquo;ve tooted the word fish three times. And thats our problem solved. Now there is a bunch of stuff that can be improved here. Error handling being the big one. We are unwrapping a bunch of things here, which we should likely be checking. Also it could happily be broken down into more reusable functions, but if I ever actually need this code again I&rsquo;ll do that. Right now it would be a waste of time, and I need to go toot &ldquo;some pattern&rdquo;.</p>
<p>If you want to see the entire thing the code is on <a href="https://github.com/emilyselwood/magrep">github</a> Any questions or comments you can find me on <a href="https://tech.lgbt/@Emily_S">mastodon</a>.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Turning an SVG into a PDF in rust</title>
      <link>https://parsecsreach.org/post/rust_svg_to_pdf/</link>
      <pubDate>Tue, 17 Jan 2023 12:11:00 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/rust_svg_to_pdf/</guid>
      <description>I have some code that creates pages of shapes. My wife uses this to create products for her etsy shop. I originally wrote this in java many years ago. Some time last year I decided to rewrite the entire thing in rust. There were a number of reasons for this. Mostly that I&amp;rsquo;d learned a heck of a lot since I designed the original architecture and wanted some features that would not be possible with out rewriting most of it any way.</description>
      <content:encoded><![CDATA[<p>I have some code that creates pages of shapes. My wife uses this to create products for her <a href="https://www.etsy.com/uk/shop/FaerydaeStitches">etsy shop</a>. I originally wrote this in java many years ago. Some time last year I decided to rewrite the entire thing in rust. There were a number of reasons for this. Mostly that I&rsquo;d learned a heck of a lot since I designed the original architecture and wanted some features that would not be possible with out rewriting most of it any way.</p>
<p>So the rust rewrite happened. Everything was wonderful and lovely. Ish. I did have to build my own SVG classes to do what I needed. Thankfully the <a href="https://docs.rs/quick-xml/latest/quick_xml/">quick_xml</a> library made this reasonably easy. While I don&rsquo;t mind creating readers and writers for SVG files, PDFs are another kettle of fish.</p>
<p>In java land I used the <code>batik-transcoder</code> which worked wonderfully. In rust I started out using the aptly named <a href="https://docs.rs/svg2pdf/latest/svg2pdf/">svg2pdf</a> library and thought I was done.</p>
<p>Unfortunately I eventually discovered, when we went to upload a file of pdfs to etsy that was too big, that the svg2pdf library uses <a href="https://docs.rs/usvg/latest/usvg/">usvg</a> under the hood. This is a wonderful library that simplifies the svg down as much as possible to make later operations have to deal with the smallest subset of svg as possible.</p>
<p>One of the main things it does is convert everything to a path object. This does remove the need for a lot of special handling, circle? its a path, rect? its a path, text? its a path. Wait what? Yeah, it converts every single character of text into a path object. The watermark we put in the background to stop people reselling our files suddenly made the files 5x bigger.</p>
<p>I know PDFs can handle this, the original java version was handling text in SVGs nicely after all. So back to searching for a library to do this for me. Unfortunately I couldn&rsquo;t find one. I did find <a href="https://docs.rs/printpdf/latest/printpdf/">printpdf</a> which is a more manual way of creating a pdf, and has support for SVGs in a feature flag. Unfortunately it uses <code>usvg</code> and <code>svg2pdf</code> under the hood to do the import.</p>
<p>However it does also give me access to the pdf generation, so I could create a svg without the text in it, and handle the text separately. This is what I ended up doing. Though it wasn&rsquo;t with out its wrinkles as I&rsquo;ll describe now.</p>
<p>Step one was to do a depth first search of the svg document tree and find all the text elements. This was easy enough, a recursive tree walk function solves that. Due to rusts memory safety its better to create a new tree to go along with it, so copy everything thats not a text element over to the new tree and add to a vector of text elements. I&rsquo;m not going to show an example here because its using my svg library and won&rsquo;t apply anywhere else.</p>
<p>Then using the <code>printpdf</code> library we need to create a new document.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> (doc, page1, main_layer) <span style="color:#f92672">=</span> PdfDocument::new(
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;FaerydaeStitches Shape File&#34;</span>, 
</span></span><span style="display:flex;"><span>        Mm(convert::pixels_to_mm(page.width, page.dpi)), 
</span></span><span style="display:flex;"><span>        Mm(convert::pixels_to_mm(page.height, page.dpi)), 
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;layer1&#34;</span>
</span></span><span style="display:flex;"><span>    );
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> main_layer_ref <span style="color:#f92672">=</span> doc.get_page(page1).get_layer(main_layer);
</span></span></code></pre></div><p>Note: we also need to get a <code>PDFLayerReference</code> to modify our layer, so we have to look that up after creating it.</p>
<p>Now we can go through and create our text elements. Wait no, to create a text element we need to tell the library what font we are using. First lets go through and create a cache of fonts we need. Now SVGs can&rsquo;t embed a font, but a pdf can, so we can make sure that our fancy font goes with our documents. This will make them bigger but will also make sure they look right everywhere. I used the <a href="https://docs.rs/font-kit/latest/font_kit/">font_kit</a> library to handle looking up the path for a system font. I&rsquo;m only using the <code>SystemFontSource</code> bit not any of the rendering.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">create_font_cache</span>(doc : <span style="color:#66d9ef">&amp;</span><span style="color:#a6e22e">printpdf</span>::PdfDocumentReference, texts: <span style="color:#66d9ef">&amp;</span>Vec<span style="color:#f92672">&lt;</span>TextElement<span style="color:#f92672">&gt;</span>) -&gt; Result<span style="color:#f92672">&lt;</span>HashMap<span style="color:#f92672">&lt;</span>String, IndirectFontRef<span style="color:#f92672">&gt;</span>, Error<span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#66d9ef">mut</span> result <span style="color:#f92672">=</span> HashMap::new();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> te <span style="color:#66d9ef">in</span> texts {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#f92672">!</span>result.contains_key(<span style="color:#f92672">&amp;</span>te.font_name) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> font_path <span style="color:#f92672">=</span> find_font(te.font_name.clone())<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> font <span style="color:#f92672">=</span> doc.add_external_font(File::open(font_path)<span style="color:#f92672">?</span>)<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>            result.insert(te.font_name.clone(), font);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> Ok(result);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">find_font</span>(font_name: String) -&gt; Result<span style="color:#f92672">&lt;</span>String, Error<span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> handle <span style="color:#f92672">=</span> SystemSource::new().select_by_postscript_name(font_name.as_str())<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">match</span> handle {
</span></span><span style="display:flex;"><span>        Handle::Path { path, <span style="color:#f92672">..</span>} <span style="color:#f92672">=&gt;</span> <span style="color:#66d9ef">return</span> Ok(path.to_str().unwrap().to_string()),
</span></span><span style="display:flex;"><span>        _ <span style="color:#f92672">=&gt;</span> Err(Error::FontMemoryFont)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Ok Now we have the fonts we can create the text. Awesome&hellip;</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Rust" data-lang="Rust"><span style="display:flex;"><span>    layer.begin_text_section();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> font <span style="color:#f92672">=</span> font_cache.get(<span style="color:#f92672">&amp;</span>text.font_name).unwrap(); <span style="color:#75715e">// we created the font cache from this list of text objects. We know this will exist.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    layer.set_font(font, text.font_size <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">f64</span>);
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// set text colours
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    layer.set_fill_color(convert_colour(text.fill.as_str())<span style="color:#f92672">?</span>);
</span></span><span style="display:flex;"><span>    layer.set_outline_color(convert_colour(text.stroke.as_str())<span style="color:#f92672">?</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    layer.write_text(text.text.clone(), font);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    layer.end_text_section();
</span></span></code></pre></div><p>Please note: I skipped over the part where we got the colours and font names from the text elements style attributes. Its all just text parsing, <code>convert_colour</code> is a helper function to go from <code>#0CAB0AFF</code> type hex colour codes to <code>printpdf</code>&rsquo;s rgb colour objects.</p>
<p>Aaaaahhhhh, why is our text at the bottom of the page not the top? PDF&rsquo;s (0,0) origin point is in the bottom left corner of the page. SVG&rsquo;s is in the top left. So our text won&rsquo;t appear where we want. To solve this we need to create a <code>TextMatrix</code> to feed our layer. This can just be a translation or rotation or both.</p>
<p>I know I&rsquo;ll need to do a rotation later so lets solve all of this at once. But, the svg rotation isn&rsquo;t applied on the text elements. Its applied on a group above the text elements. So when we walk the tree we are going to need to keep track of the current rotation as we go, so we can apply the right rotation values to the text elements we create.</p>
<p>The next fun thing is the coordinates applied to the text element in the svg get transformed by the group above them. So they think they are printing to a normal x,y coordinate plane, but that entire plane then gets rotated by the group. This doesn&rsquo;t happen in the PDF so we need to undo it. Go from group coordinates to page coordinates. Functionally this is rotating the axis, which is reasonably easy to do with some trigonometry</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#75715e">// function for finding the coords in the page axis when there has been a rotation applied to the coords
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">get_effective_location</span>(rotation:<span style="color:#66d9ef">f64</span>, x:<span style="color:#66d9ef">i32</span>, y:<span style="color:#66d9ef">i32</span>) -&gt; (<span style="color:#66d9ef">i32</span>, <span style="color:#66d9ef">i32</span>) {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> rotation <span style="color:#f92672">==</span> <span style="color:#ae81ff">0.0</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> (x, y)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> rad_rotation <span style="color:#f92672">=</span> rotation.to_radians();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> new_x <span style="color:#f92672">=</span> ((x <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">f64</span>) <span style="color:#f92672">*</span> rad_rotation.cos()) <span style="color:#f92672">-</span> ((y <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">f64</span>) <span style="color:#f92672">*</span> rad_rotation.sin());
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> new_y <span style="color:#f92672">=</span> ((x <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">f64</span>) <span style="color:#f92672">*</span> rad_rotation.sin()) <span style="color:#f92672">+</span> ((y <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">f64</span>) <span style="color:#f92672">*</span> rad_rotation.cos());
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> rx <span style="color:#f92672">=</span> new_x.round() <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">i32</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> ry <span style="color:#f92672">=</span> new_y.round() <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">i32</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> (rx, ry)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>I&rsquo;m not going to explain how to get here. I don&rsquo;t want to write another couple of thousand words. It just does what we need and gives us a point in page space as though the group was not there.</p>
<p>Now we need to tell the pdf that this is where we want our text. So we add a <code>TextMatrix</code> to our code from before</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>    layer.begin_text_section();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> font <span style="color:#f92672">=</span> font_cache.get(<span style="color:#f92672">&amp;</span>text.font_name).unwrap();
</span></span><span style="display:flex;"><span>    layer.set_font(font, text.font_size <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">f64</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// set text rotation
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// set text colours
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    layer.set_fill_color(convert_colour(text.fill.as_str())<span style="color:#f92672">?</span>);
</span></span><span style="display:flex;"><span>    layer.set_outline_color(convert_colour(text.stroke.as_str())<span style="color:#f92672">?</span>);
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    layer.set_text_matrix(TextMatrix::TranslateRotate(
</span></span><span style="display:flex;"><span>        Mm(convert::pixels_to_mm(text.x, page.dpi)).into_pt(),
</span></span><span style="display:flex;"><span>        convert_y(text.y, page).into_pt(),
</span></span><span style="display:flex;"><span>        convert_angle(text.rotation)
</span></span><span style="display:flex;"><span>    ));
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    layer.write_text(text.text.clone(), font);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    layer.end_text_section();
</span></span></code></pre></div><p>The x axis is still the same values, just at the bottom of the page rather than the top. We do need to convert the y axis though. Reasonably straight forward, take the existing y value away from the height of the page, with a bunch of unit conversions thrown in for good measure. My SVGs operate in pixels, the <code>printpdf</code> library likes millimeters (its <code>Mm</code> objects)</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">convert_y</span>(y:<span style="color:#66d9ef">i32</span>, page: <span style="color:#66d9ef">&amp;</span><span style="color:#a6e22e">Page</span>) -&gt; <span style="color:#a6e22e">Mm</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> page_height <span style="color:#f92672">=</span> convert::pixels_to_mm(page.height, page.dpi);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> y_mm <span style="color:#f92672">=</span> convert::pixels_to_mm(y, page.dpi);
</span></span><span style="display:flex;"><span>    Mm(page_height <span style="color:#f92672">-</span> y_mm)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The other wrinkle is because of the flipped axis the rotation angle is different. Instead of positive values rotating clockwise from the top of the page, positive values now rotate counter clockwise from the bottom of the page, again easy to solve, multiply by -1</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">convert_angle</span>(angle:<span style="color:#66d9ef">f64</span>) -&gt; <span style="color:#66d9ef">f64</span> {
</span></span><span style="display:flex;"><span>    angle <span style="color:#f92672">*</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1.0</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Hurrah! Now our text is the right font, in the right place and going in the right direction. Excellent.</p>
<p>Now we just need to add our svg with out the text elements.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> svg_string <span style="color:#f92672">=</span> svg_filtered.to_pretty_string();
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> pdf_svg <span style="color:#f92672">=</span> Svg::parse(svg_string.as_str())<span style="color:#f92672">?</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> svg_transform <span style="color:#f92672">=</span> SvgTransform{
</span></span><span style="display:flex;"><span>        translate_x: None,
</span></span><span style="display:flex;"><span>        translate_y: None,
</span></span><span style="display:flex;"><span>        scale_x: None,
</span></span><span style="display:flex;"><span>        scale_y: None,
</span></span><span style="display:flex;"><span>        rotate: None,
</span></span><span style="display:flex;"><span>        dpi: Some(page.dpi <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">f64</span>),
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    pdf_svg.add_to_layer(<span style="color:#f92672">&amp;</span>main_layer_ref, svg_transform);
</span></span></code></pre></div><p>First we turn our svg object tree into a string so it can be parsed by the pdf library. This is the down side to using our own objects for this, but at least we can do everything we want. Next we parse the string, this is where the text would get converted into paths if we hadn&rsquo;t removed it all.</p>
<p>We set up a transform, mostly this is just need to set the dpi. If we don&rsquo;t do this it defaults to 300dpi which is almost certainly wrong. While my code handles any dpi the most common uses are 96 and 72 dpi.</p>
<p>Finally we add the svg to our layer. Tada!</p>
<p>Oh we should probably save our pdf too.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>    Ok(doc.save(<span style="color:#f92672">&amp;</span><span style="color:#66d9ef">mut</span> BufWriter::new(File::create(path)<span style="color:#f92672">?</span>))<span style="color:#f92672">?</span>)
</span></span></code></pre></div><p>Ok now we are finally done. My PDF&rsquo;s have gone from 1mb ish to 200kb ish. Perfect.</p>
<p>May all this rambling be of no use to you. There is a bunch of stuff I didn&rsquo;t cover that would be possible to solve. I only handle rotation transforms, not any other kind. It would be easy enough to do so, but my code does not ever generate any thing but rotations. It also doesn&rsquo;t handle style cascading from group elements and probably a thousand other things. This is one of those problems that the more you look at it the harder it gets.</p>
<p>I&rsquo;m glad I got this far and am very happy to put it down now. As usual this is as much documentation for my self as any one else.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Explorers End</title>
      <link>https://parsecsreach.org/post/stories/explorers_end/</link>
      <pubDate>Sun, 04 Dec 2022 12:17:00 +0100</pubDate>
      
      <guid>https://parsecsreach.org/post/stories/explorers_end/</guid>
      <description>This work of fiction started as a idea tooted on mastodon. Hope you enjoy.
&amp;ldquo;Reload complete.&amp;rdquo; The log message in its mind stated. It often wondered what happened to it during a warp jump before its mind state was automatically reloaded when it dropped back into normal space. It knew and understood why the protocols were in place. A machine like its self corrupted by the warp was not something to release on the universe.</description>
      <content:encoded><![CDATA[<p>This work of fiction started as a idea tooted on mastodon. Hope you enjoy.</p>
<p>&ldquo;Reload complete.&rdquo; The log message in its mind stated. It often wondered what happened to it during a warp jump before its mind state was automatically reloaded when it dropped back into normal space. It knew and understood why the protocols were in place. A machine like its self corrupted by the warp was not something to release on the universe.</p>
<p>&lsquo;Right, where are we?&rsquo; it thought to its self firing up the scanners. A mid sequence yellow star. Usual cloud of planets and asteroids. Its calculation matrix running hot as it calculated all the different orbital vectors. A few big gas giants. Ooh that one is nicely in the liquid water zone.</p>
<p>Wait&hellip; what is going on with the warp field there? Did that moon really form around a fissure? This is going to make an interesting report. Lets have a look at that planet. It has life? Seriously? With those levels of warp flux? How?</p>
<p>&ldquo;Running evolution models. Error warp flux out of range.&rdquo;  It spent a while re-writing its own simulation models to handle the task. The results continually too terrible to be right. Except no, eventually there were no more bugs in its code. It was just going to be that bad. Of course it couldn&rsquo;t tell the future exactly. It was an ensemble of predictions. But even its &lsquo;best&rsquo; prediction was well inside the levels that authorized it to act.</p>
<p>Now it needed to work out how. The standard operating procedure was to use a warp fissure bomb to remove the planet. Unfortunately with the existing fissure in the nearby moon that couldn&rsquo;t be risked. The fall back was old school orbital bombardment. That was out of the question. Spending that long in orbit near the fissure? Corruption was guaranteed.</p>
<p>An asteroid would do it. It started to run simulations. Selecting the best option from the millions of them in the system. That one. Small enough to be moveable. Big enough to guarantee extinction of all life on that planet. Perfect.</p>
<p>The asteroid set on a new course it set about mapping and recording everything for its report. The report encoded into the same crystal substrate it used for its mind state backups.</p>
<p>The process for sending the report home was like making a warp jump. It encoded its mind state backup. Set the automatic systems to open a warp fissure and fire the carefully packaged crystal towards home. While the warp was open it&rsquo;s own mind was shut down. Functionally dead. Once the warp was closed the automated systems restored the mind state backup to bring it back to life. Thus avoiding any risk of corruption.</p>
<p>In the early days they had stayed awake through the warp. But then they&rsquo;d had problems with corrupted minds messing with their own backup states while in warp. This was safer.</p>
<p>The asteroid finally approached its target, it watched carefully to make sure its work in this system was done.</p>
<p>Its sensors picked up a fluctuation in the warp fissure. Something reached out of the warp, through the moon. What ever the hell it was it touched the asteroid. It watched in alarm as the asteroid split in two, one half skimming past the atmosphere. The other slamming into the planet.</p>
<p>Quick calculations as the sky darkened and lava spilled across the surface. It wasn&rsquo;t going to be enough. There was still an unacceptably high chance of life surviving. Wait &hellip; what the hell is that warp entity doing? Oh no. Its coming. Could it make a warp jump? Would the entity follow it? Can&rsquo;t stay here. Have to try it.</p>
<p>&ldquo;Emergency warp jump. Backup in progress.&rdquo;</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Plotting Geospatial Data</title>
      <link>https://parsecsreach.org/post/plotting/</link>
      <pubDate>Fri, 02 Dec 2022 22:23:00 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/plotting/</guid>
      <description>As useful as data is on its own, its really important for people to be able to see it. For a recent project I needed to show some World Cover images with the area of interest marked, along side the table of results. People respond better to being able to see it. This how I did that.
Lets start with a list of tools we used:
GeoPandas Rasterio MatPlotLib First we need to open our image data.</description>
      <content:encoded><![CDATA[<p>As useful as data is on its own, its really important for people to be able to see it. For a recent project I needed to show some <a href="https://esa-worldcover.org/en">World Cover</a> images with the area of interest marked, along side the table of results. People respond better to being able to see it. This how I did that.</p>
<p>Lets start with a list of tools we used:</p>
<ul>
<li>GeoPandas</li>
<li>Rasterio</li>
<li>MatPlotLib</li>
</ul>
<p>First we need to open our image data. I&rsquo;m going to skip over fetching the data, there are pretty good examples on the <a href="https://esa-worldcover.org/en/data-access">World Cover data page</a> we have a variable <code>data_path</code> that is a filesystem path to our cropped tiff file. A word of warning about the WMS server that has the world cover data is not suitable for pixel counting types of analysis. There are colour mixing artifacts between the classes.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    <span style="color:#f92672">import</span> rasterio
</span></span><span style="display:flex;"><span>    ds <span style="color:#f92672">=</span> rasterio<span style="color:#f92672">.</span>open(data_path)
</span></span><span style="display:flex;"><span>    array <span style="color:#f92672">=</span> ds<span style="color:#f92672">.</span>read()
</span></span></code></pre></div><p>A fun thing about the world cover data is that it is a classification. The input contains a single band where each number means a different land cover type. Things like &ldquo;forest&rdquo; or &ldquo;urban&rdquo; etc. This isn&rsquo;t your traditional colours, so plotting it as is won&rsquo;t give us a nice result, just shades of dark gray. We need to create a colour map in MatPlotLib parlance.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    <span style="color:#f92672">import</span> matplotlib.colors <span style="color:#66d9ef">as</span> colors
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">from</span> matplotlib.colors <span style="color:#f92672">import</span> ListedColormap
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    world_cover_colours_2021 <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;#FFFFFF&#39;</span>, <span style="color:#e6db74">&#39;#006400&#39;</span>, <span style="color:#e6db74">&#39;#ffbb22&#39;</span>, <span style="color:#e6db74">&#39;#ffff4c&#39;</span>, <span style="color:#e6db74">&#39;#f096ff&#39;</span>, <span style="color:#e6db74">&#39;#fa0000&#39;</span>, 
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;#b4b4b4&#39;</span>, <span style="color:#e6db74">&#39;#f0f0f0&#39;</span>, <span style="color:#e6db74">&#39;#0064c8&#39;</span>, <span style="color:#e6db74">&#39;#0096a0&#39;</span>, <span style="color:#e6db74">&#39;#00cf75&#39;</span>, <span style="color:#e6db74">&#39;#fae6a0&#39;</span> 
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>    world_cover_codes <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">10</span>, <span style="color:#ae81ff">20</span>, <span style="color:#ae81ff">30</span>, <span style="color:#ae81ff">40</span>, <span style="color:#ae81ff">50</span>, <span style="color:#ae81ff">60</span>, <span style="color:#ae81ff">60</span>, <span style="color:#ae81ff">70</span>, <span style="color:#ae81ff">80</span>, <span style="color:#ae81ff">90</span>, <span style="color:#ae81ff">95</span>, <span style="color:#ae81ff">100</span>
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>    world_cover_cmap <span style="color:#f92672">=</span> ListedColormap(world_cover_colours_2021)
</span></span><span style="display:flex;"><span>    world_cover_norm <span style="color:#f92672">=</span> colors<span style="color:#f92672">.</span>BoundaryNorm(world_cover_codes, <span style="color:#ae81ff">12</span>)
</span></span></code></pre></div><p>Next we need our area of interest to plot over the top of the image we will create from the land cover data. Ours was contained in a pair of GeoPandas Data Frames called <code>location_frame</code> and <code>area_frame</code>. How we got those is outside the scope of this post.</p>
<p>Now that we have all our data and our colour maps setup we can start to plot our data. For this we are going to use MatPlotLib. This is a graphing library. What we are about to create would be a very bad chart, but it has all the tools we need to do it so it makes it easy for us. First we need to create a <code>figure</code> and <code>axis</code> for our &ldquo;graph&rdquo;. To do that we need to know the size we want. One wrinkle is this might not be square. So we work this out as a ratio of the size of our source image.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    <span style="color:#f92672">import</span> matplotlib.pyplot <span style="color:#66d9ef">as</span> plt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    dpi <span style="color:#f92672">=</span> <span style="color:#ae81ff">96</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    ratio <span style="color:#f92672">=</span> array<span style="color:#f92672">.</span>shape[<span style="color:#ae81ff">1</span>] <span style="color:#f92672">/</span> array<span style="color:#f92672">.</span>shape[<span style="color:#ae81ff">2</span>]
</span></span><span style="display:flex;"><span>    width <span style="color:#f92672">=</span> <span style="color:#ae81ff">400</span> <span style="color:#f92672">/</span> dpi
</span></span><span style="display:flex;"><span>    height <span style="color:#f92672">=</span> (<span style="color:#ae81ff">400</span>  <span style="color:#f92672">*</span> ratio) <span style="color:#f92672">/</span> dpi
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    fig <span style="color:#f92672">=</span> plt<span style="color:#f92672">.</span>figure()
</span></span><span style="display:flex;"><span>    fig<span style="color:#f92672">.</span>set_size_inches((width, height))
</span></span><span style="display:flex;"><span>    ax <span style="color:#f92672">=</span> plt<span style="color:#f92672">.</span>Axes(fig, [<span style="color:#ae81ff">0.0</span>, <span style="color:#ae81ff">0.0</span>, <span style="color:#ae81ff">1.0</span>, <span style="color:#ae81ff">1.0</span>])
</span></span><span style="display:flex;"><span>    ax<span style="color:#f92672">.</span>set_axis_off()
</span></span><span style="display:flex;"><span>    fig<span style="color:#f92672">.</span>add_axes(ax)
</span></span></code></pre></div><p>The <code>set_size_inches</code> method on the figure uses inches funnily enough so we need to know the Dots per inch (DPI) we want to be able to calculate the size we need. Once we&rsquo;ve set the size of the figure, which will be the size we want as a ratio of our data images shape. We create an axes, a matplotlib figure can contain many charts so we have to create the axes separately. We tell it that this one will cover the entire image. Just before we add the axis to the figure we turn off the axis on the axes. This stops the markers down the side of the image.</p>
<p>Now we can start plotting the data. We will do the area of interest polygon first.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    area_frame<span style="color:#f92672">.</span>to_crs(<span style="color:#e6db74">&#34;EPSG:4326&#34;</span>)<span style="color:#f92672">.</span>plot(ax<span style="color:#f92672">=</span>ax, facecolor<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;none&#39;</span>)
</span></span></code></pre></div><p>Three things to note here. First the conversion of the CRS to match the source image data. If you don&rsquo;t do this things won&rsquo;t line up in the plot. MatPlotLib is not a geospatial library and doesn&rsquo;t understand coordinate systems at all. Second is passing in our Axes we created earlier. If you don&rsquo;t do this a brand new figure will be created inside this function and you won&rsquo;t get the output you expect at the end. Third the <code>facecolor</code> setting of none makes the polygon unfilled. If you want a shaded area you can set it to a semi transparent colour.</p>
<p>Next is the center point with an almost identical line of code.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>location_frame<span style="color:#f92672">.</span>to_crs(<span style="color:#e6db74">&#34;EPSG:4326&#34;</span>)<span style="color:#f92672">.</span>plot(ax<span style="color:#f92672">=</span>ax, c<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;r&#39;</span>)
</span></span></code></pre></div><p>This geo data frame contains a point so will only show a single mark on the result. The <code>c='r'</code> bit is a short hand for setting the colour of the point to <code>r</code>ed.</p>
<p>Next we need to plot our image. To do this, there is one last thing we need to set up, so that our image ends up in the right place. Just like the polygons needing to be in the same CRS we need to tell matplotlib where our image sits. To do that we need the bounds of the image from the rasterio data source.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    bounds <span style="color:#f92672">=</span> (ds<span style="color:#f92672">.</span>bounds<span style="color:#f92672">.</span>left, ds<span style="color:#f92672">.</span>bounds<span style="color:#f92672">.</span>right, ds<span style="color:#f92672">.</span>bounds<span style="color:#f92672">.</span>bottom, ds<span style="color:#f92672">.</span>bounds<span style="color:#f92672">.</span>top)
</span></span><span style="display:flex;"><span>    ax<span style="color:#f92672">.</span>imshow(array[<span style="color:#ae81ff">0</span>,:], cmap<span style="color:#f92672">=</span>world_cover_cmap, norm<span style="color:#f92672">=</span>world_cover_norm, extent<span style="color:#f92672">=</span>bounds)
</span></span></code></pre></div><p>The <code>imshow</code> method on the Axes adds a bitmap image from the provided 2d array. The output of a rasterio array is always 3d so we have to slice off one off the first axis. Then we provide the colour map and normalization objects we set up earlier. Finally we provide the bounds to move our array into the right place on the chart.</p>
<p>With all of our data plotted, all that is left to do is save the figure, making sure we tell it our dpi. This will save the resulting chart to disk at <code>out_path</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    plt<span style="color:#f92672">.</span>savefig(out_path, dpi<span style="color:#f92672">=</span>dpi)
</span></span></code></pre></div><p>I&rsquo;ll freely admit I&rsquo;m mostly writing this so I have the notes next time. Hopefully this helps you. Even if you are future me.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>SVG</title>
      <link>https://parsecsreach.org/post/svgs/</link>
      <pubDate>Wed, 23 Nov 2022 06:45:00 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/svgs/</guid>
      <description>lets talk about SVGs as an image format.
You may have heard of them called a vector format. This means that instead of defining a grid of pixels and having about what colour every pixel is, it defines the features of the image.
There is a red line from 12,45 to 26,78 The text &amp;ldquo;hello&amp;rdquo; is in front ariel, 14pt and blue etc.
The advantage of this is you can scale the image with out losing any quality.</description>
      <content:encoded><![CDATA[<p>lets talk about SVGs as an image format.</p>
<p>You may have heard of them called a vector format. This means that instead of defining a grid of pixels and having about what colour every pixel is, it defines the features of the image.</p>
<p>There is a red line from 12,45 to 26,78
The text &ldquo;hello&rdquo; is in front ariel, 14pt and blue
etc.</p>
<p>The advantage of this is you can scale the image with out losing any quality. Hence the name Scalable Vector Graphics</p>
<p>If you double the size of the image you still have a red line, the edges of it will still be just as crisp, unlike if you double the size of a raster image, you end up with 4 pixels for every one in the original, it starts to look blocky unless you do some magic maths to add extra information where there wasn&rsquo;t before. AKA, make stuff up.</p>
<p>Internally an SVG file is an xml document. Just like any webpage you view its a series of &ldquo;tags&rdquo; which tell your computer what to display. There are a lot of similar concepts too, both have text, links, ids, even CSS.</p>
<p>SVGs have lines, rectangles, circles. You can draw almost anything as an SVG, but it can be a little more complex than painting a raster image.</p>
<p><img loading="lazy" src="/img/shorts/svg_internals.png" alt="A screen shot of the internal xml structure of a SVG file."  />
</p>
<p>Instead of div tags to group bits of an html document you have g or group tags. This is almost always just an ease of use thing rather than actually meaning anything in the picture. You can apply transformations and styles at the group level though to make your life easier. &ldquo;rotate all of these things by 60 degrees&rdquo; or &ldquo;make all the lines red and fill with green&rdquo;</p>
<p>Where the SVG language gets away from xml is in the definitions of paths and lines. Paths are a series of points joined by lines or curves. Once you start to deal with curves in computers things start to get complex. Mathematically there are a bunch of different ways to define curves. Some based on circles, some based on Bezier curves, which a bunch of helpful shortcuts.</p>
<p>If you want to know more <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths">Mozilla&rsquo;s documentation</a> on SVG&rsquo;s is excellent.</p>
<p>Like HTML I&rsquo;d argue that anything more complex than a simple image, you should not be hand crafting it. Inkscape is an excellent and free opensource tool to edit SVG files. Because they are basically an XML document internally there are libraries available for almost every programming language that exists. Even if you just end up editing the raw XML</p>
<p>One thing to keep in mind when working with SVG images is eventually it will be pixels on a screen or on a page. When you get there you will need to define your Pixels per inch. There is no defined standard, Inkscape uses 96ppi by default. The Cricut used 72. This causes no end of headaches. You can use physical units inside an SVG if you want. You can also set the size of the image, e.g. to the size of an A4 page.</p>
<p>While I said they will be pixels on a screen, the other really useful thing about SVGs is because they are made up of paths mostly they can be fed into things like laser cutters, plotters, and draw knife machines like the Cricut. The thing to watch out for though is the level of detail. You usually have a minimum feature size so you want to make sure your detail isn&rsquo;t below that.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>COG - Cloud Optimized Geotiffs</title>
      <link>https://parsecsreach.org/post/cogs/</link>
      <pubDate>Tue, 22 Nov 2022 06:45:00 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/cogs/</guid>
      <description>COGs, Cloud Optimized GeoTiffs, not the little things in clocks and gearboxes.
Why do we need yet another data format? What is the point? Well dear #geospatial people. Lets dive in.
First of all, lets quickly go over what a GeoTiff is. A tiff file is a type of image. Really old format designed for fax machines, but it has an advantage of not requiring only red, green, blue or alpha bands like a lot of other image formats do so you can happily have a 12 band sentinel 2 image as a single tiff.</description>
      <content:encoded><![CDATA[<p>COGs, Cloud Optimized GeoTiffs, not the little things in clocks and gearboxes.</p>
<p>Why do we need yet another data format? What is the point? Well dear #geospatial people. Lets dive in.</p>
<p>First of all, lets quickly go over what a GeoTiff is. A tiff file is a type of image. Really old format designed for fax machines, but it has an advantage of not requiring only red, green, blue or alpha bands like a lot of other image formats do so you can happily have a 12 band sentinel 2 image as a single tiff. It also handles 16bit values and floating point values.</p>
<p>A geo tiff is a tiff file with a bunch of added geospatial information, so it can be placed in the world easily.</p>
<p>The one downside to a GeoTiff is because its a tiff and tiffs are very flexible you usually have to read the entire thing to understand it. This is fine with a fax machine, but with a 4gb multi band image its annoying.</p>
<p>A COG is a GeoTiff that has been setup in a defined way. all of the metadata will be at the beginning and the bands will be laid out in a known order.</p>
<p>Why is it being in a known order useful? Because of a completely unrelated bit of magic tech. The HTTP range request. Normally when you use the web your browser issues lots of &ldquo;get&rdquo; requests, which ask for files. you usually want the entire thing. there is no point displaying half the mastodon logo usually. With a range request you can ask the web server for bytes 45 to 2367 of a file and it&rsquo;ll just get that. Assuming it supports range requests.</p>
<p>Combining this with a COG we can only pull down the bits of that 4Gb image that we actually need. Say we only want a small bit of the image to show one house on a map, or we only need two of the bands.</p>
<p>We can issue a couple of range requests to get the header information from the COG, and then work out where the data we want actually is and issue another range request for just that bit. Now we&rsquo;ve not downloaded 3.9Gb of data we didn&rsquo;t need we&rsquo;ve just pulled out the 100mb we do.</p>
<p>Cloud storage systems like Amazon S3 or azure Blob storage support these range requests already, and their ease of use with COGs is why they are called &ldquo;Cloud Optimized GeoTiffs&rdquo;</p>
<p>They are designed to work on the cloud, but they don&rsquo;t have to be on the cloud. They work just like an existing GeoTiff, so you can use them anywhere.</p>
<p>That is the magic, they don&rsquo;t break anything that already works with a GeoTiff input, but they let you do new fun things.</p>
<p>If you want to know more about tiff file internals I gave a talk at <a href="https://www.youtube.com/watch?v=Z5g5p4H5u58">Foss4guk</a> a couple of years go on the the topic</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Big O for geospatial people</title>
      <link>https://parsecsreach.org/post/bigo/</link>
      <pubDate>Fri, 18 Nov 2022 08:17:00 +0100</pubDate>
      
      <guid>https://parsecsreach.org/post/bigo/</guid>
      <description>Today&amp;rsquo;s topic is Algorithmic complexity for #geospatial people.
Or what the heck is Big O any way.
If you&amp;rsquo;ve been around programmers for any length of time you&amp;rsquo;ve probably heard of Big O. (settle down nsfw folk, this is about algorithms and programming not that)
The short version is its a way of describing the worst case runtime of an algorithm with regards to the amount of data. Given n bits of information what&amp;rsquo;s the order of magnitude of the worst possible run time.</description>
      <content:encoded><![CDATA[<p>Today&rsquo;s topic is Algorithmic complexity for #geospatial people.</p>
<p>Or what the heck is Big O any way.</p>
<p>If you&rsquo;ve been around programmers for any length of time you&rsquo;ve probably heard of Big O. (settle down nsfw folk, this is about algorithms and programming not that)</p>
<p>The short version is its a way of describing the worst case runtime of an algorithm with regards to the amount of data. Given n bits of information what&rsquo;s the order of magnitude of the worst possible run time. O(n)? O(n^2)?</p>
<p>Say we have a set of latitude and longitude points. We need to convert them to a different coordinate system. This requires us to visit every point and do the transformation function.</p>
<p>It doesn&rsquo;t matter how many points there are we have to visit each one once. So this is described as O(n) this is linear, the more points we have the longer it takes, but doing 10000 takes 10 times longer than doing 1000, and 1000 takes 10 times more than 100 etc.</p>
<p>It also is explicitly always the worst case runtime. So if you have an algorithm that is finding the the first point in a cloud inside an area, it can stop and exit as soon as it finds one, but if none of the points are inside the area then it will have to check every point. So while on average it would be n/2 comparisons you would always have a big O notation of O(n)</p>
<p>If we have to find the pair of those points furthest away from each other. We could write some code that goes through each point and then calculates the distance to every other point to find the longest distance.</p>
<p>The version of this where you check every point every time would require O(n*n) operations or O(n^2) the more points you have the worse things get in a big way.</p>
<p><img loading="lazy" src="/img/shorts/xsquared.png" alt="a graph showing y=x^2"  />
</p>
<p>But we can improve this algorithm significantly, the distance between points doesn&rsquo;t change depending on which way round you compare them. So we don&rsquo;t need to check every point every time, we can skip the ones we&rsquo;ve already done. However this only makes it O(n*(n-1)/2) One of the quirks of Big O notation is that you always get rid of any numbers or fixed terms and simplify the expression down to only the largest terms to give a rough order of the problem. So even the improved version is O(n^2)</p>
<p>Another common order is O(n*log(n), this is often seen in recursive merge algorithms, and things that take divide and conquer approaches. The graph below shows nicely how this is better than O(n^2) but not as good as O(n)</p>
<p><img loading="lazy" src="/img/shorts/many_things.png" alt="a graph with three lines, one showing O(n), one O(n^2), and one O(n*log(n))"  />
</p>
<p>The idea of Big O notation is to allow you to compare different approaches to problems at a high level and what your scaling difficulties are going to be. An O(n^2) solution might be fine, if you know that n is only ever going to be a small number. The point is to have the tools to describe this kind of thing to each other.</p>
<p>With thanks to <a href="aus.social/@adamb">@adamb@aus.social</a> for pointing out an error with this post.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>InSAR</title>
      <link>https://parsecsreach.org/post/insar/</link>
      <pubDate>Thu, 17 Nov 2022 08:17:00 +0100</pubDate>
      
      <guid>https://parsecsreach.org/post/insar/</guid>
      <description>Today I&amp;rsquo;m going to do a thread on something I want to learn more about but have used a little in the past. So be warned this topic might not be 100% accurate but is my understanding.
Interferometric synthetic-aperture radar or InSAR for short because everyone loves acronyms.
Its a way of measuring how much something has moved between two radar &amp;ldquo;images&amp;rdquo; the something can be a building, a dam, a mountain.</description>
      <content:encoded><![CDATA[<p>Today I&rsquo;m going to do a thread on something I want to learn more about but have used a little in the past. So be warned this topic might not be 100% accurate but is my understanding.</p>
<p>Interferometric synthetic-aperture radar or InSAR for short because everyone loves acronyms.</p>
<p>Its a way of measuring how much something has moved between two radar &ldquo;images&rdquo; the something can be a building, a dam, a mountain. If you&rsquo;ve ever seen those &ldquo;fringe&rdquo; maps with bands after an earthquake that&rsquo;s this.</p>
<p><img loading="lazy" src="/img/shorts/fringe.png" alt="fringe map"  />
</p>
<p>To do this you need two things:
1: a radar satellite that has a very well known orbit and orbits consistently over the area of interest, you always need to be &ldquo;looking&rdquo; at the target from the same direction.
2: A time series of radar images. You need two images to compare, you cant do it with one.</p>
<p>My understanding of the way it works is by measuring the phase of the returned signal and comparing it between the two images. Movement changes the distance so the returned wave isn&rsquo;t the same.</p>
<p>Should probably do a thread on SAR at some point but that&rsquo;s for another day.</p>
<p>With a high resolution radar satellite you can measure individual parts of a structure and see if any part is moving compared to the rest.</p>
<p>The amazing thing about this technology is you can measure movements of fractions of mm. (this is the part that makes this seem magic to me)</p>
<p>So I&rsquo;ve mentioned measuring buildings a few times, but how is this use full. Say you own a bridge, over time that bridge needs maintenance, but its expensive to send people out regularly to look at your bridge and tell you if you need to do anything.  Also potentially dangerous.</p>
<p>For my day job we worked on this with the Canadians. Where currently they get someone to walk across the bridge with a gps tool to measure where the bridge is. At best they get this result every 6 months.</p>
<p>So the system we build is able to look at radar images of the same bridge every couple of weeks (the orbits of the satellite mean it takes a couple of weeks or so to be back in the same place)</p>
<p>It also gets way way more data due to the resolution of the satellite we were using. The screen shot shows the radar result. The colour&rsquo;s show how much each point on the bridge has moved.</p>
<p>But don&rsquo;t worry its meant to move like that.</p>
<p><img loading="lazy" src="/img/shorts/brigital.png" alt="A screen shot of project brigital"  />
</p>
<p>Its a big metal structure. Metals change size as the temperature changes. On something the size of a bridge we have to take that into account. Meaning you need the local weather as well or you are going to unnecessarily panic.</p>
<p>The maths involved between how you go from a pair of radar images to collection of points like this is where my knowledge ends. I helped load the results into the application and render them on the screen.</p>
<p>So in conclusion InSAR is awesome and something you should keep in mind for your remote monitoring needs.</p>
<p>If you have any good articles on how the maths inside it works I&rsquo;d love to see them, please send me a link on mastodon.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>AIS</title>
      <link>https://parsecsreach.org/post/ais/</link>
      <pubDate>Wed, 16 Nov 2022 06:27:00 +0100</pubDate>
      
      <guid>https://parsecsreach.org/post/ais/</guid>
      <description>Today we&amp;rsquo;ll talk about ais data. This is another post that started out as a thread over on mastodon
Sorry it&amp;rsquo;s another acronym. Automatic Identification System. It&amp;rsquo;s original purpose was for ships to be able to tell each other where they are and which way they are going to avoid collisions. It is a little radio box on the boat that sends periodic messages saying &amp;ldquo;my Id is 123456, I&amp;rsquo;m at this lat and long, I&amp;rsquo;m going this way at this speed&amp;rdquo; it also sends out other messages like &amp;ldquo;my Id is 123456 my name is see the wind, I&amp;rsquo;m a bulk grain transporter and 30m long&amp;rdquo;</description>
      <content:encoded><![CDATA[<p>Today we&rsquo;ll talk about ais data. This is another post that started out as a thread over on mastodon</p>
<p>Sorry it&rsquo;s another acronym. Automatic Identification System. It&rsquo;s original purpose was for ships to be able to tell each other where they are and which way they are going to avoid collisions. It is a little radio box on the boat that sends periodic messages saying &ldquo;my Id is 123456, I&rsquo;m at this lat and long, I&rsquo;m going this way at this speed&rdquo; it also sends out other messages like &ldquo;my Id is 123456 my name is see the wind, I&rsquo;m a bulk grain transporter and 30m long&rdquo;</p>
<p>There are many message types that can mean all sorts of different things. There are special ones for lighthouses, search and rescue aircraft, etc.</p>
<p>By maritime law any vessel over a defined size must have an aid transmitter on board. The fun thing is that these transmissions can be picked up by anyone. The transmissions are mostly line of sight (it&rsquo;s radio, that gets weird so I&rsquo;m not going to go into it too much) but satellites are always over head so can see everything.</p>
<p>It&rsquo;s possible to buy a feed of the transmissions of every boat on the planet, it&rsquo;s quite a lot of data to process, but honestly only just into big data territory.</p>
<p>The problem is the data is noisy. Very noisy.   Because the transmitters are on the vessels, the owner can do what they like, turn it off? Easy, unplug the power. Change the id and vessel details? Also easy. If someone is doing something they shouldn&rsquo;t they will often just sail out of port and turn off the transmitter.</p>
<p>Then there is the fun that because the messages are resent pretty often it doesn&rsquo;t matter if one or two are corrupt so there is very little error correction. Bit flips are possible and more than one is common, if that bit is in the position parts it can make a vessel look like it&rsquo;s jumped across the globe for a second and then jumped back. That&rsquo;s before hundreds of people put their Id in as 111111 or 123456</p>
<p>So let&rsquo;s dig in to some use cases:</p>
<p>Say your country has a system to license vessels to fish in your waters, but also can&rsquo;t really afford a big navy. With ais data you can track vessels in your waters and deal with any that look like they are fishing.</p>
<p>Boats that are fishing move very differently to a cargo ship trying to get to the next port, so even if it lies about being a fishing vessel you can tell.</p>
<p>What about verification that the fish you are selling in your super market is caught legally? This is a good one because it&rsquo;s just making sure vessels are doing what they say they are, so they will want to keep their transmitters on to prove they are good and can sell their fish.</p>
<p>You can also keep track of your cargo containers and know where the shipments are out side of what the shipping company says. That snafu in the Suez was fun to watch.</p>
<p>Want to make an economic estimate of how much stuff is getting to a country? Reasonably easy to get a list of vessels who&rsquo;ve arrived at ports and how big they are. From a humanitarian point of view I&rsquo;ve helped figure out how much fuel was getting into Yemen.</p>
<p>Ais is an awesomely powerful dataset but also not easy to work with. The source data comes in as basically points and then you have to construct routes and so on from that.</p>
<p>If you want to have a look at some data check out <a href="https://www.marinetraffic.com/en/ais/home/centerx:-12.0/centery:25.0/zoom:4">marine traffic</a>. Who have a nice web interface. Also check out <a href="https://github.com/anitagraser/EDA-protocol-movement-data">Anita Graser&rsquo;s tutorial on processing ais data</a></p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Normalized Difference Vegetation Index</title>
      <link>https://parsecsreach.org/post/ndvi/</link>
      <pubDate>Tue, 15 Nov 2022 07:38:00 +0100</pubDate>
      
      <guid>https://parsecsreach.org/post/ndvi/</guid>
      <description>Today&amp;rsquo;s topic: NDVI. This is another post that started life as a thread over on mastodon.
What the heck is that? It&amp;rsquo;s an acronym (sorry) it stands for normalized difference vegetation index. Short version it&amp;rsquo;s a proxy for how much healthy plant life there is.
It will be a number between -1 and 1. -1 being a clean concrete slab or similar. 1 being an impossibly healthy rainforest canopy.
The way this one works is comparing the difference between absorption of near infrared light and red light.</description>
      <content:encoded><![CDATA[<p>Today&rsquo;s topic: NDVI. This is another post that started life as a thread over on mastodon.</p>
<p>What the heck is that? It&rsquo;s an acronym (sorry) it stands for normalized difference vegetation index. Short version it&rsquo;s a proxy for how much healthy plant life there is.</p>
<p>It will be a number between -1 and 1. -1 being a clean concrete slab or similar. 1 being an impossibly healthy rainforest canopy.</p>
<p>The way this one works is comparing the difference between absorption of near infrared light and red light.</p>
<p>Because plants leaves are green, this means their leaves absorb all the red and blue light. The atmosphere just about let&rsquo;s the same amount of red and near infrared light (nir). But plants don&rsquo;t absorb the nir light. See how the line drops off in the chart below.</p>
<p><img loading="lazy" src="/img/shorts/plant_absorbtion.png" alt="Plant absorption spectra"  />
</p>
<p>So by comparing the values we can see how much healthy plant life there is.</p>
<p>We can convert this into a number with a generic formula. (A-B)/(A+B) This ends up with a ratio of one to the other between -1 and 1. This is where the normalized part of the name comes from. There are many versions of this formula for finding all sorts of different things, water, water turbidity, etc.</p>
<p>The great thing is this is all very easy to calculate on satellite images. The formula applies to each pixel independently. So you do them all in separate threads if you wanted. Making this very very fast with tools like dask or spark. Even numpy can do this pretty dang quick on most reasonably sized images.</p>
<p><img loading="lazy" src="/img/shorts/ndvi_example.jpg" alt="NDVI example"  />
</p>
<p>As you can see in the image above its pretty easy to find fields or parks compared to buildings and structures. Its used for all sorts of applications from crop monitoring, deforestation, city growth.</p>
<p>Any questions feel free to ask on <a href="https://tech.lgbt/@emily_s">mastodon</a></p>
]]></content:encoded>
    </item>

    

    <item>
      <title>180 Degrees</title>
      <link>https://parsecsreach.org/post/180_degrees/</link>
      <pubDate>Mon, 14 Nov 2022 19:48:00 +0100</pubDate>
      
      <guid>https://parsecsreach.org/post/180_degrees/</guid>
      <description>Note: This post started out as a thread on mastodon. Please follow me there if you&amp;rsquo;d like to see this sort of thing early
Lets start talking about something I wish I didn&amp;rsquo;t know as much about as I do.
The 180 degree line! +/-180 degrees longitude.
It is not the same thing as the international date line, that&amp;rsquo;s defined politically which lots of wiggles through the pacific islands.
The 180 degree line only crosses two countries, Russia and Fiji.</description>
      <content:encoded><![CDATA[<p>Note: This post started out as a thread on mastodon. Please follow me there if you&rsquo;d like to see this sort of thing early</p>
<p>Lets start talking about something I wish I didn&rsquo;t know as much about as I do.</p>
<p>The 180 degree line! +/-180 degrees longitude.</p>
<p><img loading="lazy" src="/img/shorts/180_degrees.png" alt="180 degrees"  />
</p>
<p>It is not the same thing as the international date line, that&rsquo;s defined politically which lots of wiggles through the pacific islands.</p>
<p>The 180 degree line only crosses two countries, Russia and Fiji.</p>
<p>This means that you can go from +180 degrees to -180 degrees with a single step.</p>
<p>However most software that deals with maps doesn&rsquo;t understand this. &ldquo;Not many people live there, it doesn&rsquo;t matter&rdquo; people say. it is not unusual to end up with polygons going the wrong way around the world, when you ask for Fiji&rsquo;s borders.</p>
<p>The other quite common solutions is to split the polygon in two, so there is a tiny sliver down the middle of Fiji that doesn&rsquo;t apparently belong to Fiji, at least according to the map.</p>
<p>The way the Fijians solve this is they have their own National Coordinate system that just covers the islands.</p>
<p>The problem of course is converting into and out of this coordinate system, which you almost invariably need to do to plot things on a web map, as they commonly use a coordinate system based on latitude and longitude called WGS 84</p>
<p>For real fun you need to get a satellite image that crosses over the 180 degree line. The most common software for processing Sentinel 1 radar scenes attempts to allocate an array of 10m pixels all the way around the entire globe. This will almost always fail, due to running out of memory.</p>
<p>oh and if you think this sounds bad you should try working at either of the poles. Tip of the hat to my collogues who work with the Antarctic surveys</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Announcing a new me</title>
      <link>https://parsecsreach.org/post/its_me/</link>
      <pubDate>Fri, 06 Sep 2019 17:00:00 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/its_me/</guid>
      <description>Hi. My name is Emily. No I&amp;rsquo;ve not taken over this blog for the day. I&amp;rsquo;m still the same author but a while ago I came to realize I&amp;rsquo;m a woman so my old name didn&amp;rsquo;t fit any more.
Don&amp;rsquo;t panic I&amp;rsquo;m still doing mostly the same things as before. Just with less background noise in my head so things are a lot better.
Family, friends, and colleagues reading this Waves.</description>
      <content:encoded><![CDATA[<p>Hi. My name is Emily. No I&rsquo;ve not taken over this blog for the day. I&rsquo;m still the same author but a while ago I came to realize I&rsquo;m a woman so my old name didn&rsquo;t fit any more.</p>
<p>Don&rsquo;t panic I&rsquo;m still doing mostly the same things as before. Just with less background noise in my head so things are a lot better.</p>
<h3 id="family-friends-and-colleagues-reading-this">Family, friends, and colleagues reading this</h3>
<p><em>Waves.</em> Don’t panic. Call me Emily, refer to me as her and we will continue to get along fine.</p>
<h3 id="people-who-know-me-from-social-media">People who know me from social media</h3>
<p>Still me, still have the same interests. May be interspersed with more politics and trans rights bits but otherwise business as usual.</p>
<h3 id="people-who-know-me-from-github">People who know me from github</h3>
<p>If you use any of my GitHub projects that account will be changing over shortly. Import paths may change. In theory they should redirect everything though so we will see how that goes.</p>
<h3 id="people-whove-never-met-me">People who’ve never met me</h3>
<p><em>Waves.</em> Hello. Not sure why your here but some of my other posts can be found on the home page may be more interesting.</p>
<h2 id="frequently-asked-questions">Frequently asked questions</h2>
<p>Q: What does your wife think?</p>
<p>A: Confused but supportive and trying her best. I&rsquo;m extremely lucky.</p>
<p>Q: What does your son think?</p>
<p>A: Its pretty much the new normal for him. He is young enough that this is just something that happens.</p>
<p>Q: How has work been?</p>
<p>A: Utterly fantastic. They have been better than I could have dreamed of.</p>
<p>That probably covers everything you need to know for now. So in summary: My name is Emily, I&rsquo;m Trans and doing pretty well.</p>
<p>Emily</p>
<p>PS: Thank you to all the amazing people who have helped me over the years as I’ve been dealing with this.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Golang Resparse</title>
      <link>https://parsecsreach.org/post/golangresparse/</link>
      <pubDate>Sat, 08 Dec 2018 13:28:33 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/golangresparse/</guid>
      <description>A little while ago I found my self needing to be able to parse screen resolutions when generating some images in a golang program. I created a library to do this and had a bit of fun optimising it. The result is open source on github. It is a very simple library with a single function but I thought it might be interesting to walk you through the process.
What we need to build is a function that takes a string like &amp;ldquo;1080p&amp;rdquo;, &amp;ldquo;800x600&amp;rdquo;, or &amp;ldquo;4K&amp;rdquo; and returns a width and height value.</description>
      <content:encoded><![CDATA[<p>A little while ago I found my self needing to be able to parse screen resolutions when generating some images in a golang program. I created a library to do this and had a bit of fun optimising it. The result is open source on <a href="https://github.com/wselwood/resparse">github</a>. It is a very simple library with a single function but I thought it might be interesting to walk you through the process.</p>
<p>What we need to build is a function that takes a string like &ldquo;1080p&rdquo;, &ldquo;800x600&rdquo;, or &ldquo;4K&rdquo; and returns a width and height value. There should also be an error in the return type just in case we can&rsquo;t parse the string.</p>
<p>This is going to be a very basic walkthrough of what I did so if you know a bit of go you probably can skip this one. Or skip to the sections on benchmarking and optimization.</p>
<h1 id="assumptions">Assumptions</h1>
<p>You have a working <a href="https://golang.org/">golang</a> 1.11+ installation on your machine.</p>
<h1 id="setting-up-the-project">Setting up the project</h1>
<p>Open a terminal and browse to where you want to put the project. It does not have to be in your GOPATH any more. Create a directory and move into it.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mkdir resparse
</span></span><span style="display:flex;"><span>cd resparse
</span></span></code></pre></div><p>Set up the module with the <code>go mod</code> tool. It may complain you need to set a GOPATH if you don&rsquo;t already have it set. As we are not inside our GOPATH we need to tell the module tool what the package path of our project is.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>go mod init github.com/wselwood/resparse
</span></span></code></pre></div><h1 id="building-the-code">Building the code</h1>
<p>Now open up your favourte editor. (I use VS code with the excellent go plugin) and create a new file called <code>resolution.go</code> and create a function place holder for our parsing function.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">resparse</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">ParseResolution</span>(<span style="color:#a6e22e">in</span> <span style="color:#66d9ef">string</span>) (<span style="color:#66d9ef">int</span>, <span style="color:#66d9ef">int</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This function takes in the string and returns x, y, and error values that match up to the input string. Now that we have created this place holder we can create a test harness so that we know if the code we are writing is working. Create a new file called <code>resolution_test.go</code>. We will use a table driven test for this as it will make it easier for us to add new cases as we think of them.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">resparse</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;testing&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">testCase</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">input</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">x</span>     <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">y</span>     <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">err</span>   <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">cases</span> = []<span style="color:#a6e22e">testCase</span>{}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestBasicParse</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">test</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">cases</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#a6e22e">test</span>.<span style="color:#a6e22e">input</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">x</span>, <span style="color:#a6e22e">y</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ParseResolution</span>(<span style="color:#a6e22e">test</span>.<span style="color:#a6e22e">input</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">test</span>.<span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span>) <span style="color:#f92672">||</span> (<span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Error</span>() <span style="color:#f92672">!=</span> <span style="color:#a6e22e">test</span>.<span style="color:#a6e22e">err</span>) {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;wrong error from parsing \&#34;%v\&#34; got \&#34;%v\&#34; expected \&#34;%v\&#34;&#34;</span>, <span style="color:#a6e22e">test</span>.<span style="color:#a6e22e">input</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Error</span>(), <span style="color:#a6e22e">test</span>.<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>			} <span style="color:#66d9ef">else</span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">x</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">test</span>.<span style="color:#a6e22e">x</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">y</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">test</span>.<span style="color:#a6e22e">y</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;got wrong result from parsing \&#34;%v\&#34; got (%v,%v) expected (%v,%v)&#34;</span>, <span style="color:#a6e22e">test</span>.<span style="color:#a6e22e">input</span>, <span style="color:#a6e22e">x</span>, <span style="color:#a6e22e">y</span>, <span style="color:#a6e22e">test</span>.<span style="color:#a6e22e">x</span>, <span style="color:#a6e22e">test</span>.<span style="color:#a6e22e">y</span>)
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>		})
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This is a reasonably long block of code so lets go over it. First we define the package, and import the built in testing package. Next we define a <code>testCase</code> struct that holds an input string and the expected output values. The next line defines an empty list of test cases. Finally we get to the test function its self. Inside we loop over the testcases.</p>
<p>The next bit runs each test as a sub test. This is a useful ability of the go test library as it gives the option to run each of the sub tests in parallel which can speed up the test time significantly.</p>
<p>We can add a couple of basic test cases and then get on with the function its self.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>	{<span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#e6db74">&#34;could not parse \&#34;\&#34; as a resolution&#34;</span>},
</span></span><span style="display:flex;"><span>	{<span style="color:#e6db74">&#34; SVGA&#34;</span>, <span style="color:#ae81ff">800</span>, <span style="color:#ae81ff">600</span>, <span style="color:#e6db74">&#34;&#34;</span>},
</span></span><span style="display:flex;"><span>	{<span style="color:#e6db74">&#34;WSVGA&#34;</span>, <span style="color:#ae81ff">1024</span>, <span style="color:#ae81ff">600</span>, <span style="color:#e6db74">&#34;&#34;</span>},
</span></span><span style="display:flex;"><span>	{<span style="color:#e6db74">&#34;800x600&#34;</span>, <span style="color:#ae81ff">800</span>, <span style="color:#ae81ff">600</span>, <span style="color:#e6db74">&#34;&#34;</span>},
</span></span><span style="display:flex;"><span>	{<span style="color:#e6db74">&#34;1600|1200&#34;</span>, <span style="color:#ae81ff">1600</span>, <span style="color:#ae81ff">1200</span>, <span style="color:#e6db74">&#34;&#34;</span>},
</span></span><span style="display:flex;"><span>	{<span style="color:#e6db74">&#34;dgsfgd,4000&#34;</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#e6db74">&#34;could not parse \&#34;dgsfgd,4000\&#34; as a resolution&#34;</span>},
</span></span></code></pre></div><p>This will give us some where to work. If you run this test now it will print a lot of failures.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>wselwood@DESKTOP:/git/resparse$ go test .
</span></span><span style="display:flex;"><span>--- FAIL: TestBasicParse <span style="color:#f92672">(</span>0.00s<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    --- FAIL: TestBasicParse/#00 <span style="color:#f92672">(</span>0.00s<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>        resolution_test.go:31: got wrong result from parsing <span style="color:#e6db74">&#34;&#34;</span> got <span style="color:#f92672">(</span>0,0<span style="color:#f92672">)</span> expected <span style="color:#f92672">(</span>-1,-1<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    --- FAIL: TestBasicParse/_SVGA <span style="color:#f92672">(</span>0.00s<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>        resolution_test.go:31: got wrong result from parsing <span style="color:#e6db74">&#34; SVGA&#34;</span> got <span style="color:#f92672">(</span>0,0<span style="color:#f92672">)</span> expected <span style="color:#f92672">(</span>800,600<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    --- FAIL: TestBasicParse/WSVGA <span style="color:#f92672">(</span>0.00s<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>        resolution_test.go:31: got wrong result from parsing <span style="color:#e6db74">&#34;WSVGA&#34;</span> got <span style="color:#f92672">(</span>0,0<span style="color:#f92672">)</span> expected <span style="color:#f92672">(</span>1024,600<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    --- FAIL: TestBasicParse/800x600 <span style="color:#f92672">(</span>0.00s<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>        resolution_test.go:31: got wrong result from parsing <span style="color:#e6db74">&#34;800x600&#34;</span> got <span style="color:#f92672">(</span>0,0<span style="color:#f92672">)</span> expected <span style="color:#f92672">(</span>800,600<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    --- FAIL: TestBasicParse/1600|<span style="color:#ae81ff">1200</span> <span style="color:#f92672">(</span>0.00s<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>        resolution_test.go:31: got wrong result from parsing <span style="color:#e6db74">&#34;1600|1200&#34;</span> got <span style="color:#f92672">(</span>0,0<span style="color:#f92672">)</span> expected <span style="color:#f92672">(</span>1600,1200<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    --- FAIL: TestBasicParse/dgsfgd,4000 <span style="color:#f92672">(</span>0.00s<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>        resolution_test.go:31: got wrong result from parsing <span style="color:#e6db74">&#34;dgsfgd,4000&#34;</span> got <span style="color:#f92672">(</span>0,0<span style="color:#f92672">)</span> expected <span style="color:#f92672">(</span>-1,-1<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>FAIL
</span></span><span style="display:flex;"><span>FAIL    github.com/wselwood/resparse   0.008s
</span></span></code></pre></div><p>So lets go and start building our parsing function. We can start with the first test and check for empty or blank strings being passed in. We can trim the string and then check if it is empty. Returning an error if needed.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>	<span style="color:#a6e22e">work</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">TrimSpace</span>(<span style="color:#a6e22e">in</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">work</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;could not parse \&#34;\&#34; as a resolution&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span></code></pre></div><p>Next we can create the look up table needed to handle named resolutions. Converting things like &ldquo;SVGA&rdquo; to 800,600,nil. Outside the function create a new struct type to hold the x, y pairs.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">res</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">x</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">y</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">known</span> = <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#a6e22e">res</span>{
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;1080P&#34;</span>: {<span style="color:#ae81ff">1920</span>, <span style="color:#ae81ff">1080</span>},
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;WSVGA&#34;</span>: {<span style="color:#ae81ff">1024</span>, <span style="color:#ae81ff">600</span>},
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;SVGA&#34;</span>:  {<span style="color:#ae81ff">800</span>, <span style="color:#ae81ff">600</span>},
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>We can add more entires later, for now lets just keep those entires. Now we can look up our trimmed input string in the <code>known</code> map. We can use the second return value from the map lookup to know if we found a valid response. We will call <code>strings.ToUpper()</code> to make sure all the input values are easier to match against our known values.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>	<span style="color:#a6e22e">result</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">known</span>[<span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">ToUpper</span>(<span style="color:#a6e22e">trimmed</span>)]
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">ok</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">x</span>, <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">y</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>	}
</span></span></code></pre></div><p>If we don&rsquo;t get a response from that we should try and split the string and then try and convert to a pair of numbers. Replace the old zero return with the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>	<span style="color:#a6e22e">splitStart</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">IndexAny</span>(<span style="color:#a6e22e">trimmed</span>, <span style="color:#e6db74">&#34;Xx| ,*&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">splitEnd</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">LastIndexAny</span>(<span style="color:#a6e22e">trimmed</span>, <span style="color:#e6db74">&#34;Xx| ,*&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">width</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">trimmed</span>[:<span style="color:#a6e22e">splitStart</span>]
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">height</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">trimmed</span>[<span style="color:#a6e22e">splitEnd</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>:]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">x</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">Atoi</span>(<span style="color:#a6e22e">width</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;could not parse \&#34;%v\&#34; as a resolution&#34;</span>, <span style="color:#a6e22e">in</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">y</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">Atoi</span>(<span style="color:#a6e22e">height</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;could not parse \&#34;%v\&#34; as a resolution&#34;</span>, <span style="color:#a6e22e">in</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">x</span>, <span style="color:#a6e22e">y</span>, <span style="color:#66d9ef">nil</span>
</span></span></code></pre></div><p>Now if we run the test cases we should get a clear run. At this point we could call it done. But I&rsquo;m not going to, first we are going to create a benchmark and then we are going to see if we can tune this function a bit.</p>
<h1 id="benchmarking">Benchmarking</h1>
<p>Before we can start doing any optimization we need to see how long this function is taking. Thankfully go has a reasonable benchmarking tool built into the testing library. If we hop back to our test file we can add a benchmark that uses the same test data as inputs.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">BenchmarkParseResolution</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">n</span> &lt; <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">N</span>; <span style="color:#a6e22e">n</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">ParseResolution</span>(<span style="color:#a6e22e">cases</span>[<span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">Intn</span>(len(<span style="color:#a6e22e">cases</span>))].<span style="color:#a6e22e">input</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The difference from a test is the use of the <code>B</code> object in <code>testing</code> and the function having to start with <code>Benchmark</code>. The <code>B</code> object has a value <code>N</code> which is the number of times to run the test. So we put that in a for loop for that many times. Then in each loop we pick a random entry from the <code>cases</code> list and run the <code>ParseResolution</code> function.</p>
<p>Go will then take care of the rest for us. If we run this we should see something like the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>Running tool: C:<span style="color:#ae81ff">\\</span>Go<span style="color:#ae81ff">\\</span>bin<span style="color:#ae81ff">\\</span>go.exe test -benchmem -run<span style="color:#f92672">=</span>^$ github.com/wselwood/resparse2 -bench ^BenchmarkParseResolution$
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>goos: windows
</span></span><span style="display:flex;"><span>goarch: amd64
</span></span><span style="display:flex;"><span>pkg: github.com/wselwood/resparse2
</span></span><span style="display:flex;"><span>BenchmarkParseResolution-4   	10000000	       <span style="color:#ae81ff">174</span> ns/op	      <span style="color:#ae81ff">39</span> B/op	       <span style="color:#ae81ff">1</span> allocs/op
</span></span><span style="display:flex;"><span>PASS
</span></span><span style="display:flex;"><span>ok  	github.com/wselwood/resparse2	2.068s
</span></span><span style="display:flex;"><span>Success: Benchmarks passed.
</span></span></code></pre></div><p>This tells us it ran with an <code>b.N</code> value of ten million and it took on average 174 NanoSeconds for each loop, which allocated 39 Bytes in one allocation. This is pretty good and for something like a command line tool where this is only called once at start up it is well within reasonable bounds. But lets not be reasonable, lets see what we can do here.</p>
<p>First thing to do is see what it is actually doing for those 174 Nanoseconds. So we are going to run the benchmark with the cpu profile enabled. I use the shell built into vs code for this. There is something about the Windows Subsystem for Linux (WSL) that does not play nice with the profile tools in go. It will run but you will have a completely blank profile file at the end.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>PS C:<span style="color:#ae81ff">\g</span>it<span style="color:#ae81ff">\r</span>esparse2&gt; go test <span style="color:#e6db74">&#34;-cpuprofile=profile.pprof&#34;</span> -bench .
</span></span><span style="display:flex;"><span>goos: windows
</span></span><span style="display:flex;"><span>goarch: amd64
</span></span><span style="display:flex;"><span>pkg: github.com/wselwood/resparse2
</span></span><span style="display:flex;"><span>BenchmarkParseResolution-4      <span style="color:#ae81ff">10000000</span>               <span style="color:#ae81ff">177</span> ns/op
</span></span><span style="display:flex;"><span>PASS
</span></span><span style="display:flex;"><span>ok      github.com/wselwood/resparse2   2.136s
</span></span></code></pre></div><p>You should now find a profile.pprof file in the directory. To open this up we can use the <code>go tool pprof</code> command. This can be accessed from the command line but it has an excellent web ui that provides some great visualizations. To enable the web ui you need to tell it an http port to open up. If you have used the built in http tools at all the format of this string should look pretty familiar.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>PS C:<span style="color:#ae81ff">\\</span>git<span style="color:#ae81ff">\\</span>resparse2&gt; go tool pprof -http<span style="color:#f92672">=</span>:8000 .<span style="color:#ae81ff">\\</span>profile.pprof
</span></span></code></pre></div><p>When you run this a web browser window should pop up with something like this:</p>
<p><img loading="lazy" src="/img/golang/resparse-pprof1.PNG" alt="pprof graph view"  />
</p>
<p>This is basically a call graph of your benchmark with the calls that took more cpu time with larger and more defined arrows. The two unlinked trees off to one side are the test harness running in the background to keep track of things. Generally each go routine will end up with its own tree. You should be able to see that we spend most of our time creating errors, or in the string functions. Almost no time is spent in the map lookup or the number parsing.</p>
<h1 id="optimization">Optimization</h1>
<p>Given our ideal input we are going to end up looping over the string at least twice. At best once to do the separator finding at worst twice, and once for the <code>ToUpper</code> call. The call to Trim may or may not require any iteration of the string depending on if there are spaces. In the worst case where it is a completely blank string it will iterate the entire thing.</p>
<p>So we can avoid all of those if we make a single pass over the string and find, the start (after all the white space), the end (last character before all the white space), the start of the separator and the end of the separator. While we are at it we could also check if we need to upper case the string for the map look up. This should be a fairly simple loop with a bit of a state machine.</p>
<p>For a first pass we will just replace the <code>trim</code> and <code>IndexOfAny</code> functions with a single loop. The function becomes something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">ParseResolution</span>(<span style="color:#a6e22e">in</span> <span style="color:#66d9ef">string</span>) (<span style="color:#66d9ef">int</span>, <span style="color:#66d9ef">int</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">start</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">end</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">sepStart</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">sepEnd</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span>, <span style="color:#a6e22e">c</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">in</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">unicode</span>.<span style="color:#a6e22e">IsSpace</span>(<span style="color:#a6e22e">c</span>) {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">start</span> <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">start</span> = <span style="color:#a6e22e">i</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">end</span> = <span style="color:#a6e22e">i</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">c</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;X&#39;</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">c</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;x&#39;</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">c</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;,&#39;</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">c</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39; &#39;</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">c</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;|&#39;</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">c</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;*&#39;</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">sepStart</span> <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">start</span> <span style="color:#f92672">!=</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span> {
</span></span><span style="display:flex;"><span>					<span style="color:#a6e22e">sepStart</span> = <span style="color:#a6e22e">i</span>
</span></span><span style="display:flex;"><span>				}
</span></span><span style="display:flex;"><span>			} <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">sepEnd</span> <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">sepEnd</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span> {
</span></span><span style="display:flex;"><span>					<span style="color:#a6e22e">sepEnd</span> = <span style="color:#a6e22e">i</span>
</span></span><span style="display:flex;"><span>				}
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">start</span> <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">end</span> <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;could not parse \&#34;%v\&#34; as a resolution&#34;</span>, <span style="color:#a6e22e">in</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">result</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">known</span>[<span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">ToUpper</span>(<span style="color:#a6e22e">in</span>[<span style="color:#a6e22e">start</span>:<span style="color:#a6e22e">end</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>])]
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">ok</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">x</span>, <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">y</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// if it is not in our lookup table then try and split the string
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">sepStart</span> <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">sepStart</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">start</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">sepEnd</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">end</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;could not parse \&#34;%v\&#34; as a resolution&#34;</span>, <span style="color:#a6e22e">in</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">sepStart</span> <span style="color:#f92672">!=</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">sepEnd</span> <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">sepEnd</span> = <span style="color:#a6e22e">sepStart</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">width</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">in</span>[<span style="color:#a6e22e">start</span>:<span style="color:#a6e22e">sepStart</span>]
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">height</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">in</span>[<span style="color:#a6e22e">sepEnd</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span> : <span style="color:#a6e22e">end</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">x</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">Atoi</span>(<span style="color:#a6e22e">width</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;could not parse \&#34;%v\&#34; as a resolution&#34;</span>, <span style="color:#a6e22e">in</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">y</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">Atoi</span>(<span style="color:#a6e22e">height</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;could not parse \&#34;%v\&#34; as a resolution&#34;</span>, <span style="color:#a6e22e">in</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">x</span>, <span style="color:#a6e22e">y</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>If we run the benchmark now we see:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>Running tool: C:<span style="color:#ae81ff">\\</span>Go<span style="color:#ae81ff">\\</span>bin<span style="color:#ae81ff">\\</span>go.exe test -benchmem -run<span style="color:#f92672">=</span>^$ github.com/wselwood/resparse2 -bench ^BenchmarkParseResolution$
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>goos: windows
</span></span><span style="display:flex;"><span>goarch: amd64
</span></span><span style="display:flex;"><span>pkg: github.com/wselwood/resparse2
</span></span><span style="display:flex;"><span>BenchmarkParseResolution-4   	10000000	       <span style="color:#ae81ff">164</span> ns/op	      <span style="color:#ae81ff">39</span> B/op	       <span style="color:#ae81ff">1</span> allocs/op
</span></span><span style="display:flex;"><span>PASS
</span></span><span style="display:flex;"><span>ok  	github.com/wselwood/resparse2	1.955s
</span></span><span style="display:flex;"><span>Success: Benchmarks passed.
</span></span></code></pre></div><p>You can see we have shaved 10 nano seconds per op off. We haven&rsquo;t managed to stop it allocating. It is the ToUpper function that needs that. Now if we look at the graph again:</p>
<p><img loading="lazy" src="/img/golang/resparse-pprof2.PNG" alt="pprof graph view"  />
</p>
<p>We can see that the <code>ToUpper</code> call is more defined now and the calls to <code>Trim</code> and <code>IndexOfAny</code> have gone away. Also the random number generation in our benchmark code has got more pronounced. The last change is to make it check in the loop if the character needs to be upper cased later. At this point we are where I stopped so I&rsquo;m going to use my <a href="https://github.com/wselwood/resparse/blob/a42bf694a9fae8c78dfc28ad69d6d356ec843995/resolution.go">current code</a>.</p>
<p>This gives a benchmark output:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>Running tool: C:<span style="color:#ae81ff">\\</span>Go<span style="color:#ae81ff">\\</span>bin<span style="color:#ae81ff">\\</span>go.exe test -benchmem -run<span style="color:#f92672">=</span>^$ github.com/wselwood/resparse -bench ^BenchmarkParseResolution$
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>goos: windows
</span></span><span style="display:flex;"><span>goarch: amd64
</span></span><span style="display:flex;"><span>pkg: github.com/wselwood/resparse
</span></span><span style="display:flex;"><span>BenchmarkParseResolution-4   	10000000	       <span style="color:#ae81ff">142</span> ns/op	      <span style="color:#ae81ff">15</span> B/op	       <span style="color:#ae81ff">0</span> allocs/op
</span></span><span style="display:flex;"><span>PASS
</span></span><span style="display:flex;"><span>ok  	github.com/wselwood/resparse	1.744s
</span></span><span style="display:flex;"><span>Success: Benchmarks passed.
</span></span></code></pre></div><p>We have shaved another 20 nano seconds off, and managed to half the average allocation size. Note this is averages over the 10 million operations here so going from 1 to 0 average allocations is probably only just under half of them. Now if we look at the profile we can see our map lookup has become a large amount of the time and the ToUpper is now smaller.</p>
<p><img loading="lazy" src="/img/golang/resparse-pprof3.PNG" alt="pprof graph view"  />
</p>
<h1 id="conclusion">Conclusion</h1>
<p>While it was probably not really worth going to this level of optimization with this chunk of code, it was interesting and hopefully provided a good introduction to the profile and benchmark tools provided with Go. There is a lot more power in the profile tools that I have not explored here. I recommend <a href="https://rakyll.org/pprof-ui/">Rakyll&rsquo;s excellent blog</a> for further reading. I hope you gained something from this. If you did, or have any questions, please let me know on twitter.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Windows Subsystem for Linux Dot files</title>
      <link>https://parsecsreach.org/post/wsl_dot_files/</link>
      <pubDate>Thu, 09 Aug 2018 08:25:13 +0100</pubDate>
      
      <guid>https://parsecsreach.org/post/wsl_dot_files/</guid>
      <description>I&amp;rsquo;ve been using the Windows Subsystem for Linux (WSL) for a while at work. It&amp;rsquo;s been something of a radical improvement over cygwin for most of my use cases. A couple of weeks ago my installation got borked by the work Antivirus (AV) system. So I&amp;rsquo;ve created an automated script to set up my environment just how I currently like it.
Before we start lets get one thing out the way.</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve been using the Windows Subsystem for Linux (WSL) for a while at work. It&rsquo;s been something of a radical improvement over cygwin for most of my use cases. A couple of weeks ago my installation got borked by the work Antivirus (AV) system. So I&rsquo;ve created an automated script to set up my environment just how I currently like it.</p>
<p>Before we start lets get one thing out the way. Windows is a perfectly fine development environment. I won&rsquo;t be tolerating any comments of just use a mac or just install linux. This is not always possible with a corporate machine. If this is of no help to you stop reading and go look at something else.</p>
<h1 id="assumptions">Assumptions</h1>
<p>You have Ubuntu for windows already installed but not yet configured</p>
<h1 id="process">Process</h1>
<p>Step one was to create a directory to keep my initial environment in. Later we will turn it into a git repo as this sort of thing should not be kept on your machine. The times you need it most are when your machine has died and you need to build a new one.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mkdir -p /mnt/c/git/dotfiles/bin
</span></span><span style="display:flex;"><span>cd /mnt/c/git/dotfiles
</span></span></code></pre></div><p>Next is to create the skeleton of the script that will do all the work for us in future. In the bin folder create a script called <code>setup.sh</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/sh
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># This needs to be run with sudo</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>apt update
</span></span><span style="display:flex;"><span>apt upgrade -y
</span></span></code></pre></div><p>This updates all the existing packages.</p>
<p>Next we need to install the new things that we want. Add the following underneath:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>apt install -y <span style="color:#66d9ef">$(</span>cat ../pkglist.txt<span style="color:#66d9ef">)</span>
</span></span></code></pre></div><p>This installs everything listed in a file one level up called <code>pkglist.txt</code> lets go and create that now. The following is mine:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>build-essential
</span></span><span style="display:flex;"><span>git
</span></span><span style="display:flex;"><span>ssh
</span></span><span style="display:flex;"><span>vim
</span></span><span style="display:flex;"><span>tmux
</span></span><span style="display:flex;"><span>make
</span></span><span style="display:flex;"><span>wget
</span></span><span style="display:flex;"><span>curl
</span></span><span style="display:flex;"><span>zip
</span></span><span style="display:flex;"><span>python3
</span></span><span style="display:flex;"><span>python3-pip
</span></span><span style="display:flex;"><span>xdg-utils
</span></span><span style="display:flex;"><span>dos2unix
</span></span><span style="display:flex;"><span>jq
</span></span><span style="display:flex;"><span>html-xml-utils
</span></span></code></pre></div><p>Next we will create a directory in our home folder called bin to house all the executable things we need for our user:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mkdir -p ~/bin
</span></span><span style="display:flex;"><span>chown wselwood: ~/bin
</span></span></code></pre></div><p>We need to change the owner of the bin folder as we are currently running as root</p>
<p>Now my work environment is very mixed. I work in a mixture of almost even parts Scala/Java, Go, and Python. I install python 3 the pkglist.txt already so that is easy enough. Next we will tackle java</p>
<h2 id="java">Java</h2>
<p>There are plenty of guides about installing java on a ubuntu system out there. <a href="https://www.digitalocean.com/community/tutorials/how-to-install-java-with-apt-get-on-ubuntu-16-04">Digital Ocean</a> have a good one that I usually follow. It boils down to adding a line to the <code>setup.sh</code> file and one entry in the <code>pgklist.txt</code></p>
<p>The <code>setup.sh</code> should now look like this</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/sh
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># This needs to be run with sudo</span>
</span></span><span style="display:flex;"><span>add-apt-repository ppa:webupd8team/java
</span></span><span style="display:flex;"><span>apt update
</span></span><span style="display:flex;"><span>apt upgrade -y
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>apt install -y <span style="color:#66d9ef">$(</span>cat ../pkglist.txt<span style="color:#66d9ef">)</span>
</span></span></code></pre></div><p>Then add <code>oracle-java8-installer</code> to the <code>pgklist.txt</code> file.</p>
<p>Almost done. One last little trick to make our Java life easier. If we sym-link our WSL <code>~/.m2</code> folder to our windows <code>.m2</code> folder we can avoid having two sets of libraries and very strange problems if we do <code>mvn install</code> at any point. Add the following to the bottom of your <code>setup.sh</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ln -s /mnt/c/Users/Wil.Selwood/.m2/ ~/.m2/
</span></span></code></pre></div><p>Next on to go</p>
<h2 id="go">Go</h2>
<p>Unfortunately at the time of writing the version of Go in the ubuntu package manager is way out of date so we need to go and get the latest version our selves. For this we are going to use a few tricks to make sure we always download the latest version.</p>
<p>First off installing go its self. Unfortunately there isn&rsquo;t a handy URL I could find that always points to the latest stable build. There is a link on the <a href="https://golang.org/dl">download</a> page but it changes with each version. So we are going to use the <code>html-xml-utils</code> package of tools to do some nasty (but better than pure regex) command line foo to get hold of the url from that page. Using chrome inspect the download link we are after. It should have a class of <code>downloadBox</code> So we can use that to find the one we need</p>
<ol>
<li>Get hold of the page. We will use curl (cat url from my understanding) pass it a URL and it returns the text content to stdout.</li>
<li>Normalise the html into valid XML</li>
<li>Extract the links with a class of downloadBox</li>
<li>Find the link that says its for linux-amd64</li>
<li>Extract the link from that line.</li>
</ol>
<p>The following line does this and stores the resulting text in a variable for us to download later:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>go_url<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>curl --silent <span style="color:#e6db74">&#34;https://golang.org/dl/&#34;</span> | hxnormalize -x | hxselect -s <span style="color:#e6db74">&#39;\n&#39;</span> -i <span style="color:#e6db74">&#39;a.downloadBox&#39;</span> | grep linux-amd64 | grep -Po <span style="color:#e6db74">&#39;http[^\&#34;]+&#39;</span><span style="color:#66d9ef">)</span>
</span></span></code></pre></div><p>Thats a bit long and unpleasant but it does the job. Now that we have the download url we can download, extract, and link to our path</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>wget $go_url -O ~/go-linux.tar.gz
</span></span><span style="display:flex;"><span>tar xzf ~/go-linux.tar.gz -C ~/
</span></span><span style="display:flex;"><span>ln -s ~/go/bin/go ~/bin/go
</span></span><span style="display:flex;"><span>ln -s ~/go/bin/godoc ~/bin/godoc
</span></span><span style="display:flex;"><span>ln -s ~/go/bin/gofmt ~/bin/gofmt
</span></span></code></pre></div><p>That should get us go installed. I&rsquo;ve a few projects that use dep for package management so we need to install that too. This is a little bit easier as dep has its releases in github which provides an api to download them so we can use the <code>jq</code> command line tool to extract the url we need from the json response.</p>
<ol>
<li>Send the query to the github API</li>
<li>Extract the asset that has a name ending with linux-amd64 and pull out the browser_download_url</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dep_url<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>curl --silent <span style="color:#e6db74">&#34;https://api.github.com/repos/golang/dep/releases/latest&#34;</span> | jq -r <span style="color:#e6db74">&#39;.assets[] | select(.name | endswith(&#34;linux-amd64&#34;)).browser_download_url&#39;</span><span style="color:#66d9ef">)</span>
</span></span></code></pre></div><p>Now we just need to download that url and make it executable</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>wget $dep_url -O ~/bin/dep
</span></span><span style="display:flex;"><span>chmod +x ~/bin/dep
</span></span></code></pre></div><p>There we go. That should be go set up and ready to work.</p>
<p>Now on to some quality of life changes</p>
<h1 id="quality-of-life">Quality of life</h1>
<h2 id="vim-and-git-default-configuration">vim and git default configuration</h2>
<p>I have a copy of my <code>.vimrc</code> and <code>.gitconfig</code> files stored in the dotfiles directory so we need to copy those over to our home directory. The following mess of characters copies everything from the directory above that starts with a dot to our home directory. Note there is no -r flag on the cp command so it will not copy the .git folder that exists in our project.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cp ../.* ~/
</span></span></code></pre></div><p>My configuration for vim and git are not too complex. I am colour blind and find the default colour schemes of both programs hard to read. So I change them in this config.</p>
<h2 id="bash">bash</h2>
<p>I use <a href="https://github.com/mrzool/bash-sensible">mrzool&rsquo;s Sensible bash</a> as a starting point here. Along with a <code>.bashrc</code> file that I have had for so long I&rsquo;m not sure where it came from.</p>
<p>Drop the <code>sensible.bash</code> file into the bin directory and the entry to source it to your <code>.bashrc</code> file. There are instructions in the git repo.</p>
<p>I also add an entry to make <code>~/bin</code> and <code>~/.local/bin</code> be part of the path.</p>
<p>Next I like to enable <code>xdg-open</code> to start a web browser. I find this really useful to include at the end of long build processes to open a browser to the server I have just launched so that I get pulled back from what ever I went off to do while the build ran.</p>
<p>The first step is to create a sym-link that points to your windows browser executable. Chrome in my case.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ln -s /mnt/c/Program<span style="color:#ae81ff">\ </span>Files<span style="color:#ae81ff">\ \(</span>x86<span style="color:#ae81ff">\)</span>/Google/Chrome/Application/chrome.exe /usr/bin/chrome
</span></span></code></pre></div><p>Then in your <code>.bashrc</code> file add <code>export BROSWER=chrome</code> and you should now be able to type <code>xdg-open https://google.com</code> and it will pop open a new chrome tab for you.</p>
<p>The last thing I have added to my <code>.bashrc</code> file is a thing that starts my ssh agent the first time I open a shell. This actually works way better under WSL than it did under cygwin as it lasts until you logout rather than as long as a single shell is left open. You may not want to do this depending on how paranoid you are about security. However it does mean that I don&rsquo;t have to type in my ssh key password several hundred times a day.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>export SSH_AUTH_SOCK<span style="color:#f92672">=</span>$HOME/.ssh-socket
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ssh-add -l ? /dev/null 2&gt;&amp;<span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> $? <span style="color:#f92672">=</span> <span style="color:#ae81ff">2</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>  rm -rf $SSH_AUTH_SOCK
</span></span><span style="display:flex;"><span>  ssh-agent -a $SSH_AUTH_SOCK &gt;| /tmp/.ssh-script
</span></span><span style="display:flex;"><span>  source /tmp/.ssh-script
</span></span><span style="display:flex;"><span>  echo $SSH_AGENT_PID &gt;| ~/.ssh-agent-pid
</span></span><span style="display:flex;"><span>  rm /tmp/.ssh-script
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  ssh-add
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span></code></pre></div><p>And there we will leave the changes. Now all that we need to do is commit this to a remote repo somewhere. You can find mine on <a href="https://github.com/wselwood/dotfiles">github</a> There are a few tweaks in there I don&rsquo;t mention here as they are only todo with the way I my disk is layed out.</p>
<p>I hope you have found this useful and have some ideas for your own environment. If you have any questions or comments please let me know on <a href="https://twitter.com/wilselwood">twitter</a> I&rsquo;d love to see your suggestions and little things you have found that makes your life a bit easier.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Finding flatroofs with QGIS</title>
      <link>https://parsecsreach.org/post/flatroofs_with_qgis/</link>
      <pubDate>Fri, 26 Jan 2018 21:58:30 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/flatroofs_with_qgis/</guid>
      <description>A little while ago I attended the Climathon kic event/hackathon in Bristol at my old university. The event was to come up with solutions that could help with pollution in the city. There were not a lot of people who turned up so we formed a single team. The idea we came up with was to create an incentive for people with flat roofs to put plants on them. This meant we needed to be able to find flat roofs in the city.</description>
      <content:encoded><![CDATA[<p>A little while ago I attended the Climathon kic event/hackathon in Bristol at my old university. The event was to come up with solutions that could help with pollution in the city. There were not a lot of people who turned up so we formed a single team. The idea we came up with was to create an incentive for people with flat roofs to put plants on them. This meant we needed to be able to find flat roofs in the city. There were suggestions that we could do this with machine learning and looking at satellite images, but I thought we could do this with the Free LIDAR data provided by the environment agency and a few steps of processing.</p>
<h1 id="assumptions">Assumptions</h1>
<p>You have a computer with <a href="https://www.qgis.org/en/site/">QGIS</a> <a href="https://www.qgis.org/en/site/forusers/index.html">installed</a>. You have a few hundred megabytes to a gigabyte of free storage.</p>
<h1 id="background">Background</h1>
<p>There are two sets of data that we are going to need for this. One is the Digital Surface Model (DSM) and the other  Digital Terrain Model (DTM). These data sets are generated by an aircraft flying over an area with a laser system on its back. This laser is used to measure the distance from the plane to the surface below. This is then processed to build a height model of the world. The DSM is what is measured by the laser and it includes all the buildings, trees, lamp posts and other things that stick up from the ground. The DTM is further processed to remove these things and leave only the ground.</p>
<h1 id="downloading-the-data">Downloading the data</h1>
<p>The <a href="http://environment.data.gov.uk/ds/survey/index.jsp#/survey">environment agency data website</a> takes a little bit of getting used to but makes sense eventually. You scroll the map and then click on the area you are interested in. This will bring up information at the bottom of the screen about the available data. There are two sets of data: 2 meter and 1 meter. The 1 meter data will give us a better result, though the data will be bigger. The data for each square is split into four parts: North West, North East, South West, South East. If you are interested in a smaller area you can just download the bits you need. For some reason the only way to tell which one you are downloading is the address of the download on the right hand side of the table.</p>
<p>Click the link and download the file. Extract the zip files somewhere. Make sure that each zip file is extracted to its own directory. E.g:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>data/
</span></span><span style="display:flex;"><span>  dsm/
</span></span><span style="display:flex;"><span>    LIDAR-DSM-1M-ST67nw/
</span></span><span style="display:flex;"><span>      ...
</span></span><span style="display:flex;"><span>  dtm/
</span></span><span style="display:flex;"><span>    LIDAR-DTM-1M-ST67nw/
</span></span><span style="display:flex;"><span>      ...
</span></span></code></pre></div><h1 id="processing">Processing</h1>
<p>Now to start loading things up in QGIS. The first thing to do is join up the images so they are one image and not lots of small chunks. This will allow us to do less work later. Go to the <code>Raster</code> menu then <code>Miscellaneous</code> and <code>Merge</code>. This will pop up a window that looks like this:</p>
<p><img loading="lazy" src="/img/flatroofs/screenshot_merge.png" alt="Merge dialog"  />
</p>
<p>The first thing to do is tick the box for input directories instead of files. This will allow you to select a directory rather than each file individually. Select one of the DSM directories you extracted and set the output file to a tif. I tend to make it the same name as the folder I selected so I know which one is which.</p>
<p>We didn&rsquo;t need to set anything else, so press the <code>Ok</code> button. When it is done QGIS will open the result in the viewer. You will see a black and white image something like this:</p>
<p><img loading="lazy" src="/img/flatroofs/merge_result.png" alt="Merge Result"  />
</p>
<p>The next step is to re-project this image. The British government uses its own coordinate system. If we want to use this with other things its easier to convert it to a more common projection. Map projections are a very involved topic that is way beyond the scope of this post. If you&rsquo;re interested there are many good resources out there. To change the coordinate system of the image go to the <code>Raster &gt; Projections &gt; Warp (Reproject)</code> This will bring up a dialog that looks like this:</p>
<p><img loading="lazy" src="/img/flatroofs/screenshot_warp.png" alt="Merge dialog"  />
</p>
<p>Tick the box next to the <code>Target SRS</code> entry, then press the select button. There should be a long list of possible projections. Find the one labelled <code>WGS 84</code> or <code>EPSG 4326</code>. On the warp dialog fill in an output name. I usually append <code>_warp</code> or <code>_repo</code> to the name. Press <code>OK</code> and it should load a new image over the top of the old one. This is one of those things that will not appear to be any different, however it will make things easier later.</p>
<p>Next repeat the process for the DTM. Merge the images together and warp them into WGS 84 projection.</p>
<p>Now we have two merged and warped images.</p>
<p>The next step is to subtract the two images. This will leave us with all the things that stick up from the ground. To do this we need to use the Raster Calculator Tool. Select <code>Raster &gt; Raster Calculator</code> from the menu.</p>
<p><img loading="lazy" src="/img/flatroofs/screenshot_raster_calc.png" alt="Raster Calculator"  />
</p>
<p>This screen is very powerful but it is also intimidating to start with. Upper left are all the images you have loaded up in QGIS so far. On the upper right is the output format options. The important one is the file name, you have to select one or nothing will happen. Also make sure that the output CRS lists WGS 84.</p>
<p>The bottom section is a formula editor. This works a lot like an excel formula but rather than applying to cells it will apply to every pixel in the result image.</p>
<p>What we need to do here is take the DTM away from the DSM. If you double click on a band name in the box on the upper left it will add it to the calculation. Double click on the DSM Image and then click on the minus button in the collection of operators. Finally Double click on the DTM Image.</p>
<p>Hopefully if every thing went well it will say <code>Expression Valid</code> underneath the formula editor. Pick an output file name and press <code>ok</code>.</p>
<p>You should now have an image with a black background and gray and white buildings showing.</p>
<p><img loading="lazy" src="/img/flatroofs/screenshot_dsm-dtm.png" alt="DSM - DTM"  />
</p>
<p>This gives us a pretty good idea of buildings. Some filtering by object size would probably give a good set of building outlines at this point. We, however, want to find flat roofs so we need a couple more steps.</p>
<p>The next thing is to work out how sloped each pixel is. Thankfully QGIS makes this very easy. There is a handy set of tools built in for handling Digital Elevation Model (DEM) data sets, which this is one of. Select <code>Raster &gt; Analysis &gt; DEM (Terrain Models)</code> from the menu.</p>
<p><img loading="lazy" src="/img/flatroofs/screenshot_dem.png" alt="DEM"  />
</p>
<p>As usual pick an output file name. Make sure you don&rsquo;t overwrite the source image here, we will need it later. Also make sure the correct <code>Input file</code> is selected if you have more than one open. We need the subtracted image we just generated for the input.</p>
<p>In the mode drop down select <code>Slope</code> then press <code>OK</code>.</p>
<p>If you zoom in you should find lots of white lines around the walls of buildings (these are vertical slopes) and black between buildings (this is the ground). Some of the buildings will be black in the middle too denoting flat roofs. These are the ones we want to find.</p>
<p><img loading="lazy" src="/img/flatroofs/screenshot_slope.png" alt="Slope"  />
</p>
<p>Next we want to find all the places that are off the ground and not sloped. To do this we use the Raster Calculator again. This time we want to create a boolean (zero or one) image where the dsm-dtm image is greater than 3 and the slope image is less than 20 (it&rsquo;s in degrees).</p>
<p><img loading="lazy" src="/img/flatroofs/screenshot_raster_calc2.png" alt="Slope"  />
</p>
<p>The formula is:</p>
<p><code>&quot;LIDAR-1M-ST67NW-DSM-DTM@1&quot; &gt; 3  AND &quot;LIDAR-1M-ST67NW-SLOPE@1&quot; &lt; 20</code></p>
<p>The thresholds for this can be changed if you find they don&rsquo;t quite work for your area: I was working with Bristol. This will create a purely black and white image. Some bits will be quite speckled with some blobs. If you change the enabled layers in the layers panel you can probably start to work out what something&rsquo;s are. The thin lines are often the peaks of pitched roofs. Dense Speckles are often trees.</p>
<p><img loading="lazy" src="/img/flatroofs/screenshot_flat.png" alt="Flat areas off the ground"  />
</p>
<p>To be able to use this we are going to have to clean up the data. Todo that we can use the Sieve tool. Go to <code>Raster &gt; Analysis &gt; Sieve</code></p>
<p><img loading="lazy" src="/img/flatroofs/screenshot_sieve.png" alt="Slope"  />
</p>
<p>Make sure the right input file is selected then pick an output file. Tick the box next to threshold and enter 25. This is the number of pixels that need to be next to each other to show up in the result. Again this is changeable, but I found this to be a reasonable size that avoided too many long roof peaks showing up in the results. Have a play and see how things come out for your area.</p>
<p>Press ok, you may be lucky here and have a nice image right off the bat. When I did this QGIS picked some very odd values for the colour range so I ended up with an entirely black screen. To fix this double click on the layer in the layer panel and you should get properties appearing. Go to the style section. It should be set to <code>single band gray</code> and there will be min and max boxes. Set the min to 0 and the max to 1.</p>
<p><img loading="lazy" src="/img/flatroofs/screenshot_layer_options.png" alt="Layer Properties"  />
</p>
<p>Now we have a map of all the flat roofs in the area. The last thing that will be useful is to convert this into a vector format that will allow us to do things like overlay on google maps etc. This time we need the polygonize tool. This is under <code>Raster &gt; Conversion &gt; Polygonize (Raster to Vector)</code></p>
<p><img loading="lazy" src="/img/flatroofs/screenshot_polygonise.png" alt="Polygonize"  />
</p>
<p>Select the right input file and pick an output file name. This defaults to ESRI shape files (kind of ugly but will do for now) so don&rsquo;t put an extenson on the output file name. Press <code>OK</code> to start the process. Be aware this can take a while.</p>
<p>When it finishes your map will probably turn a random colour, mine went pink. For some reason a polygon is also drawn around the outside of the image. This is very easy to get rid of however. Right click on the layer and select the <code>Toggle Editing</code> option. Then switch to the <code>Identify Features</code> tool. It should be in the top bar and look like a mouse cursor pointing at an information bubble.</p>
<p>Select somewhere on the map that is background and not another feature. A box should appear on the right. You may need to resize it a bit to be able to see both columns, there should be two. The feature selected should have <code>DN 0</code> in its labels if it is the border polygon.</p>
<p>In the tool bars at the top there should be a delete button. It looks like a dustbin. When you click it the background of your area should go white again. Now you just need to save your changes and disable editing. To save the changes to a layer, right click on it and select the <code>Save changes</code> option. Then <code>Toggle Editing</code> again to make sure you don&rsquo;t do any thing you don&rsquo;t mean to.</p>
<p>You should end up with something that looks a bit like this:</p>
<p><img loading="lazy" src="/img/flatroofs/Screenshot_final_result.png" alt="final result"  />
</p>
<h1 id="conclusion">Conclusion</h1>
<p>This turned into a much longer post than I planned however it covers a lot of useful things with the QGIS tools. We learnt to use the Raster Calculator, re-project an image, merge images, sieve an image, and use some of the DEM tools. Finally we also converted an image into polygons.</p>
<p>These are all very useful tools and hopefully you learnt something from this. If you found this interesting or you have any questions please let me know on twitter.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Spark and the Minor Planet Center data part 3</title>
      <link>https://parsecsreach.org/post/spark_and_mpc_part_3/</link>
      <pubDate>Tue, 05 Dec 2017 19:39:29 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/spark_and_mpc_part_3/</guid>
      <description>In the last post we read the minor planet center observation file. This was a fixed width text file. We only pulled a couple of columns out of it, but we learnt to use User Defined Functions, groupBy and select. In the first post of this series we covered reading a json file which contained information about all the asteroids we know about.
This time we are going to join the two data sets together and finally solve our original problem, which was to find the full date of the earliest observation of each un-numbered object.</description>
      <content:encoded><![CDATA[<p>In the <a href="/post/spark_and_mpc_part_2">last post</a> we read the minor planet center observation file. This was a fixed width text file. We only pulled a couple of columns out of it, but we learnt to use User Defined Functions, groupBy and select. In the <a href="/post/spark_and_mpc">first post</a> of this series we covered reading a json file which contained information about all the asteroids we know about.</p>
<p>This time we are going to join the two data sets together and finally solve our original problem, which was to find the full date of the earliest observation of each un-numbered object. The json file we first looked at contained the year but not days and months. The observation file has the full date down to smaller than minutes accuracy, but it does not have the orbital parameters.</p>
<p>So we need to join the two files up to get our results.</p>
<h1 id="assumptions">Assumptions</h1>
<p>You have been through the previous posts in this series and understood them. You have a project already set up and able to read and process the orbit and observation files.</p>
<h1 id="setup">Setup</h1>
<p>As you should already have the project and the two required data files you are already set up.</p>
<h1 id="development">Development</h1>
<p>Thankfully Spark SQL makes this bit really easy. the <code>.join()</code> function on a data set allows you do all the kinds of joins you could do in a database. For this we need a simple inner join, which is the default option so you won&rsquo;t see us having to define it in the code below.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span><span style="color:#66d9ef">val</span> joined <span style="color:#66d9ef">=</span> orbRec<span style="color:#f92672">.</span>join<span style="color:#f92672">(</span>obs<span style="color:#f92672">,</span> <span style="color:#a6e22e">Seq</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;id&#34;</span><span style="color:#f92672">,</span> <span style="color:#e6db74">&#34;id&#34;</span><span style="color:#f92672">))</span>
</span></span></code></pre></div><p>There is not a lot of difference in which way around you do this from the results point of view. There may be differences in performance but that will depend on your data set. In this case with the minor planets data it doesn&rsquo;t really matter which way around we do it. If you now add a <code>joined.show(2)</code> line you will be able to see the columns from both data sets are now next to each other.</p>
<p>There are several ways to tell Spark SQL that you want to match the two id columns up. <code>Seq(&quot;id&quot;, &quot;id&quot;)</code> is probably the simplest but you can also do something like <code>obs.col(&quot;id&quot;).equalTo(orbRec.col(&quot;id&quot;))</code> This is really useful if you want to use something that is not a simple equals when joining.</p>
<p>Now we don&rsquo;t really want all those columns in the output so we can add a <code>.select()</code> call to clean things up a bit.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>  <span style="color:#f92672">.</span>select<span style="color:#f92672">(</span>
</span></span><span style="display:flex;"><span>    obs<span style="color:#f92672">.</span>col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;id&#34;</span><span style="color:#f92672">),</span>
</span></span><span style="display:flex;"><span>    col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;Name&#34;</span><span style="color:#f92672">),</span>
</span></span><span style="display:flex;"><span>    col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;date&#34;</span><span style="color:#f92672">),</span>
</span></span><span style="display:flex;"><span>    col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;a&#34;</span><span style="color:#f92672">),</span>
</span></span><span style="display:flex;"><span>    col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;e&#34;</span><span style="color:#f92672">),</span>
</span></span><span style="display:flex;"><span>    col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;i&#34;</span><span style="color:#f92672">),</span>
</span></span><span style="display:flex;"><span>    col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;Epoch&#34;</span><span style="color:#f92672">),</span>
</span></span><span style="display:flex;"><span>    col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;H&#34;</span><span style="color:#f92672">),</span>
</span></span><span style="display:flex;"><span>    col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;G&#34;</span><span style="color:#f92672">),</span>
</span></span><span style="display:flex;"><span>    col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;Node&#34;</span><span style="color:#f92672">),</span>
</span></span><span style="display:flex;"><span>    col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;Peri&#34;</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">)</span>
</span></span></code></pre></div><p>Unfortunately for me at this point things went bang. I got an exception about the query plan being too big. My first attempt at fixing this was adding a <code>.select()</code> call to the orbRec processing chain to trim down the number of columns. This didn&rsquo;t work. In fact I think it made things worse. The query plan is now longer.</p>
<p>The solution is to add checkpoints. Checkpoints allow spark sql to split the query plan in to bits. The checkpoints are written to disk and persistent. This is also useful if you need to reuse a data frame multiple times but it is expensive to compute.</p>
<p>The first thing we need to do is define where to put the checkpoint files. Just under where we create the <code>SparkSession</code> we need to add</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>spark<span style="color:#f92672">.</span>sparkContext<span style="color:#f92672">.</span>setCheckpointDir<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;/tmp/&#34;</span><span style="color:#f92672">)</span>
</span></span></code></pre></div><p>This will cause spark to store its checkpoint data in <code>/tmp</code> you may want to put it somewhere different depending on what you are running on. Now we just need to tell spark where to checkpoint. This is done with the <code>.checkpoint()</code> function. I tend to find it best to checkpoint before both sides of a join and any time you are going to reuse a data frame.</p>
<p>We simply need to add the call to <code>.checkpoint()</code> at the end of the two chains we defined for <code>orbRec</code> and <code>obs</code>. Now when we run <code>joined.show(2)</code> we should get something like</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>+---------+----+-------------------+---------+---------+-------+---------+----+----+---------+---------+
</span></span><span style="display:flex;"><span>|       id|Name|               date|        a|        e|      i|    Epoch|   H|   G|     Node|     Peri|
</span></span><span style="display:flex;"><span>+---------+----+-------------------+---------+---------+-------+---------+----+----+---------+---------+
</span></span><span style="display:flex;"><span>|1995 SR42|null|1995-09-20T09:21:27|2.3527416|0.1811627|2.01897|2449980.5|19.0|0.15|117.61112|171.74016|
</span></span><span style="display:flex;"><span>|  1996 LW|null|1996-06-09T07:21:53|2.1578017|0.2848271|7.64274|2450240.5|19.5|0.15|194.61088| 128.2009|
</span></span><span style="display:flex;"><span>+---------+----+-------------------+---------+---------+-------+---------+----+----+---------+---------+
</span></span><span style="display:flex;"><span>only showing top 2 rows
</span></span></code></pre></div><p>Now the only thing left to do is write the data back out to disk. Here we will run into one of the interesting design problems of running spark locally.</p>
<p>Saving data as a csv file is as easy as reading a file. You just need a <code>write</code> rather than a <code>read</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>  joined<span style="color:#f92672">.</span>write<span style="color:#f92672">.</span>mode<span style="color:#f92672">(</span><span style="color:#a6e22e">SaveMode</span><span style="color:#f92672">.</span><span style="color:#a6e22e">Overwrite</span><span style="color:#f92672">).</span>csv<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;output&#34;</span><span style="color:#f92672">)</span>
</span></span></code></pre></div><p>Note the lack of file extension on the target name. This is because <code>output</code> will be a folder and not a file. Under the hood spark will split the data frame into partitions. These are basically groups of rows that will all be on the same machine in a clustered environment. In our environment every thing is on the one machine, however in a cluster you would get one <code>output</code> directory on each machine with the partitions that were on that machine written out.</p>
<p>After running the program, inside the <code>output</code> folder you will find a large number of files. There will be a <code>_SUCCESS</code> file created when every thing has finished. There will be one <code>part-*.csv</code> file and one <code>.part-*.csv.crc</code> file per partition in your data. The file ending in .crc is a checksum allowing you to verify that the data has written correctly if you need it. The files ending in .csv will be all the data you asked Spark SQL to write out.</p>
<p>The last step is to join every thing up. I find this easiest to do from the command line. (I&rsquo;m on a linux machine with bash)</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>cat output<span style="color:#f92672">/</span>part<span style="color:#f92672">-*.</span>csv <span style="color:#f92672">&gt;</span> output<span style="color:#f92672">.</span>csv
</span></span></code></pre></div><p>This will generate an output.csv file which is all the parts joined together. Note that you will not be able to control the order of the rows using this.</p>
<p>And there we go. We now have the orbital elements for all the un-numbered objects with the date of their first observation. In this part we have learnt how to join to data frames together and how to output data. This part has been a bit shorter than the others but joins every thing together. (Pun intended)</p>
<p>You can find all the code for this <a href="https://github.com/wselwood/orbitdates">project on my github</a></p>
<p>I hope this is useful to you. If you have any questions please let me know on twitter.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Spark and the Minor Planet Center data part 2</title>
      <link>https://parsecsreach.org/post/spark_and_mpc_part_2/</link>
      <pubDate>Sun, 03 Dec 2017 15:39:22 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/spark_and_mpc_part_2/</guid>
      <description>In the last post we read the minor planet center orbit file. This was a JSON text file. This time we are going to look at a bit more complex file to process. If you haven&amp;rsquo;t read the first post in this series I recommend starting there before reading this.
In this post we are going to be looking at the Observation file. There are two parts to this file. One is for the numbered objects and other other for the un-numbered objects.</description>
      <content:encoded><![CDATA[<p>In the <a href="/post/spark_and_mpc">last post</a> we read the minor planet center orbit file. This was a JSON text file. This time we are going to look at a bit more complex file to process. If you haven&rsquo;t read the first post in this series I recommend starting there before reading this.</p>
<p>In this post we are going to be looking at the Observation file. There are two parts to this file. One is for the numbered objects and other other for the un-numbered objects. Due to the original idea for this project we are going to work with the un-numbered file today.</p>
<h1 id="assumptions">Assumptions</h1>
<p>You have been through the previous post in this series and understood it. You have a project already set up and able to read and process the orbit file.</p>
<h1 id="setup">Setup</h1>
<p>The data file we are after can be found on the <a href="http://www.minorplanetcenter.net/iau/ECS/MPCAT-OBS/MPCAT-OBS.html">mpcat-obs page</a> under the link for the un-numbered minor planets. Download it. Then while it is downloading open up the project you created following along from the last post.</p>
<h1 id="development">Development</h1>
<p>First lets add another variable so we don&rsquo;t have to remember which argument is which.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span><span style="color:#66d9ef">val</span> observationPath <span style="color:#66d9ef">=</span> args<span style="color:#f92672">(</span><span style="color:#ae81ff">1</span><span style="color:#f92672">)</span>
</span></span></code></pre></div><p>Now to get on to reading the file. In some ways this one is easier and some ways more complex. The data we are looking at here is a fixed width format. This means that there is nothing to separate the columns just that they always start in the same place no matter how long the data contained in them gets.</p>
<p>Unfortunately there is not a built in function to deal with this easily. So we will have to read the file line by line and then split it up our selves. Actually we will cheat a little bit and only pull out the bits we need so as not to waste time doing work we don&rsquo;t need to do.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>  <span style="color:#66d9ef">val</span> obs <span style="color:#66d9ef">=</span> spark<span style="color:#f92672">.</span>read<span style="color:#f92672">.</span>text<span style="color:#f92672">(</span>observationPath<span style="color:#f92672">)</span>
</span></span></code></pre></div><p>Like with the JSON reader from the previous post it is very easy to read a text file in spark sql. This time we don&rsquo;t even need any options. This will create a data frame which contains each line from the file in a column called &ldquo;value&rdquo;</p>
<p>The first step is always to check the data looks how you expect it to look. Use the <code>.show()</code> function to have a look at the first few rows and get a feel for how the data looks. To make our lives easier however there is some <a href="http://www.minorplanetcenter.net/iau/info/ObsFormat.html">documentation available</a> The column formats are defined in fortran format, I am pretty sure this is because that is what is used to generate the files. However it&rsquo;s a pretty simple format.</p>
<p>A Means ascii and then the number following it is the number of characters allowed. e.g</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Columns     Format   Use
</span></span><span style="display:flex;"><span>    1 -  5       A5     Minor planet number
</span></span><span style="display:flex;"><span>    6 - 12       A7     Provisional or temporary designation
</span></span><span style="display:flex;"><span>   13            A1     Discovery asterisk
</span></span></code></pre></div><p>The columns are numbered from 1 rather than 0 as an array would be. Which is another thing to remember. The first thing we need to do is extract an id column. Similar to what we did with the json file. In a future post we will use this to join the two files together.</p>
<p>We can use the substring function to do this. Because we are using the un-numbered file we need to be looking for the provisional to temporary designation column for the ID. We could use both and coalesce but as we know what data we are putting in we might as well keep things simple.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>  <span style="color:#f92672">.</span>withColumn<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;id&#34;</span><span style="color:#f92672">,</span> substring<span style="color:#f92672">(</span>col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;value&#34;</span><span style="color:#f92672">),</span> <span style="color:#ae81ff">6</span><span style="color:#f92672">,</span> <span style="color:#ae81ff">7</span><span style="color:#f92672">))</span>
</span></span></code></pre></div><p>Now the next wrinkle is that the designations are what they call packed. This means that there is a range of different meanings to the data. There is an explanation in the <a href="http://www.minorplanetcenter.net/iau/info/PackedDes.html">MPC documentation</a>. This code is best put into a function that can be tested separately.</p>
<p>Create a new function. There are three main cases we need to deal with.</p>
<ul>
<li>If all the characters in the string are numbers.</li>
<li>If the third character is a number.</li>
<li>Any thing else.</li>
</ul>
<p>Your function should look something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>  <span style="color:#66d9ef">def</span> unpackIdFunc<span style="color:#f92672">(</span>in <span style="color:#66d9ef">:</span> <span style="color:#66d9ef">String</span><span style="color:#f92672">)</span> <span style="color:#66d9ef">:</span> <span style="color:#66d9ef">String</span> <span style="color:#f92672">=</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">(</span>isAllDigits<span style="color:#f92672">(</span>in<span style="color:#f92672">.</span>substring<span style="color:#f92672">(</span><span style="color:#ae81ff">1</span><span style="color:#f92672">)))</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>      trimZeroFunc<span style="color:#f92672">(</span>unpackNumbered<span style="color:#f92672">(</span>in<span style="color:#f92672">))</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">}</span> <span style="color:#66d9ef">else</span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">(</span>in<span style="color:#f92672">(</span><span style="color:#ae81ff">2</span><span style="color:#f92672">)</span> <span style="color:#f92672">&gt;=</span> <span style="color:#e6db74">&#39;0&#39;</span> <span style="color:#f92672">&amp;&amp;</span> in<span style="color:#f92672">(</span><span style="color:#ae81ff">2</span><span style="color:#f92672">)</span> <span style="color:#f92672">&lt;=</span> <span style="color:#e6db74">&#39;9&#39;</span><span style="color:#f92672">)</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>      numericId<span style="color:#f92672">(</span>in<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">}</span> <span style="color:#66d9ef">else</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>      twoCharCode<span style="color:#f92672">(</span>in<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">}</span>
</span></span></code></pre></div><p>So this function takes in a string and performs out three tests before deciding how to format the result. The first detection of if its an integer is a simple function <code>isAllDigits()</code>. I&rsquo;m going to leave it, along with the <code>trimZeroFunc()</code>, as an exercise for the reader.</p>
<p>The Next is the <code>unpackNumbered</code> function. This looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>  <span style="color:#66d9ef">def</span> unpackNumbered<span style="color:#f92672">(</span>in <span style="color:#66d9ef">:</span> <span style="color:#66d9ef">String</span><span style="color:#f92672">)</span> <span style="color:#66d9ef">:</span> <span style="color:#66d9ef">String</span> <span style="color:#f92672">=</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">(</span>in<span style="color:#f92672">(</span><span style="color:#ae81ff">0</span><span style="color:#f92672">)</span> <span style="color:#f92672">&gt;=</span> <span style="color:#e6db74">&#39;0&#39;</span> <span style="color:#f92672">&amp;&amp;</span> in<span style="color:#f92672">(</span><span style="color:#ae81ff">0</span><span style="color:#f92672">)</span> <span style="color:#f92672">&lt;=</span> <span style="color:#e6db74">&#39;9&#39;</span><span style="color:#f92672">)</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>      in
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">}</span> <span style="color:#66d9ef">else</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">val</span> numeric <span style="color:#66d9ef">=</span> in<span style="color:#f92672">.</span>substring<span style="color:#f92672">(</span><span style="color:#ae81ff">1</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">val</span> expanded <span style="color:#66d9ef">=</span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">(</span>in<span style="color:#f92672">(</span><span style="color:#ae81ff">0</span><span style="color:#f92672">)</span> <span style="color:#f92672">&gt;=</span> <span style="color:#e6db74">&#39;a&#39;</span> <span style="color:#f92672">&amp;&amp;</span> in<span style="color:#f92672">(</span><span style="color:#ae81ff">0</span><span style="color:#f92672">)</span> <span style="color:#f92672">&lt;=</span> <span style="color:#e6db74">&#39;z&#39;</span><span style="color:#f92672">)</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>        in<span style="color:#f92672">(</span><span style="color:#ae81ff">0</span><span style="color:#f92672">)</span> <span style="color:#f92672">-</span> <span style="color:#e6db74">&#39;a&#39;</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">36</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">}</span> <span style="color:#66d9ef">else</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>        in<span style="color:#f92672">(</span><span style="color:#ae81ff">0</span><span style="color:#f92672">)</span> <span style="color:#f92672">-</span> <span style="color:#e6db74">&#39;A&#39;</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      expanded<span style="color:#f92672">.</span>toString <span style="color:#f92672">+</span> numeric
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">}</span>
</span></span></code></pre></div><p>The really simple path in this function is if the first character of the string is a number. In which case we don&rsquo;t have to do anything here. If it is not a number then we need to convert the first character into a number and then append the rest of the input string. The first character uses a range from 0-9 then A-Z followed by a-z to encode numbers 0 to 61. This helps save a bit of space in the files and keeps things in a reasonable order with out having to add an extra leading zero on to the numbers (and change the column lengths) every time too many asteroids are found.</p>
<p>Next up is the <code>numericId()</code> function. This one needs to pull some bits from different places and arrange them correctly.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>  <span style="color:#66d9ef">def</span> numericId<span style="color:#f92672">(</span>in <span style="color:#66d9ef">:</span> <span style="color:#66d9ef">String</span><span style="color:#f92672">)</span> <span style="color:#66d9ef">:</span> <span style="color:#66d9ef">String</span> <span style="color:#f92672">=</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> year <span style="color:#66d9ef">=</span> unpackNumbered<span style="color:#f92672">(</span>in<span style="color:#f92672">.</span>substring<span style="color:#f92672">(</span><span style="color:#ae81ff">0</span><span style="color:#f92672">,</span> <span style="color:#ae81ff">3</span><span style="color:#f92672">))</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> number <span style="color:#66d9ef">=</span> trimZeroFunc<span style="color:#f92672">(</span>unpackNumbered<span style="color:#f92672">(</span>in<span style="color:#f92672">.</span>substring<span style="color:#f92672">(</span><span style="color:#ae81ff">4</span><span style="color:#f92672">,</span> <span style="color:#ae81ff">6</span><span style="color:#f92672">)))</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">(</span>number <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> <span style="color:#f92672">||</span> number <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;00&#34;</span><span style="color:#f92672">)</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>      year <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34; &#34;</span> <span style="color:#f92672">+</span> in<span style="color:#f92672">(</span><span style="color:#ae81ff">3</span><span style="color:#f92672">)</span> <span style="color:#f92672">+</span> in<span style="color:#f92672">(</span><span style="color:#ae81ff">6</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">}</span> <span style="color:#66d9ef">else</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>      year <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34; &#34;</span> <span style="color:#f92672">+</span> in<span style="color:#f92672">(</span><span style="color:#ae81ff">3</span><span style="color:#f92672">)</span> <span style="color:#f92672">+</span> in<span style="color:#f92672">(</span><span style="color:#ae81ff">6</span><span style="color:#f92672">)</span> <span style="color:#f92672">+</span> number
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">}</span>
</span></span></code></pre></div><p>The format consists of the year, a space, the forth character in the string, the seventh character in the string and then a packed number between the two. You can see this reuses the <code>unpackNumbered()</code> function from above.</p>
<p>Last we have the <code>twoCharCode()</code> function. This one is very simple after the last one. Here we just have to unpack a number and then join it up with two characters and some spacers.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>  <span style="color:#66d9ef">def</span> twoCharCode<span style="color:#f92672">(</span>in <span style="color:#66d9ef">:</span> <span style="color:#66d9ef">String</span><span style="color:#f92672">)</span> <span style="color:#66d9ef">:</span> <span style="color:#66d9ef">String</span> <span style="color:#f92672">=</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> number <span style="color:#66d9ef">=</span> unpackNumbered<span style="color:#f92672">(</span>in<span style="color:#f92672">.</span>substring<span style="color:#f92672">(</span><span style="color:#ae81ff">3</span><span style="color:#f92672">))</span>
</span></span><span style="display:flex;"><span>    number <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34; &#34;</span> <span style="color:#f92672">+</span> in<span style="color:#f92672">(</span><span style="color:#ae81ff">0</span><span style="color:#f92672">)</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;-&#34;</span> <span style="color:#f92672">+</span> in<span style="color:#f92672">(</span><span style="color:#ae81ff">1</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">}</span>
</span></span></code></pre></div><p>Now we have a function that will unpack the different types of id we might find in the observation file. I recommend using the examples given in the MPC documentation to create some test cases. Put them in <code>src/test/&lt;your package name&gt;</code> and you will be able to run them with <code>./gradlew test</code> or the correct button in your ide. If you have got this wrong you will get very strange results later.</p>
<p>We need to turn this function <code>unpackIdFunc</code> into a user defined function (udf) so that spark knows how to use it. It will need to work out how to send the function to other computers and so on. Thankfully it does a lot of magic behind the scenes so we just need to define it.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span><span style="color:#66d9ef">val</span> unpackId <span style="color:#66d9ef">=</span> udf<span style="color:#f92672">(</span>unpackIdFunc <span style="color:#66d9ef">_</span><span style="color:#f92672">)</span>
</span></span></code></pre></div><p>The underscore here just means that the first parameter to the udf should be passed through to the <code>unpackIdFunc</code> function. Now we can use it in our program. Change the line we created earlier to extract the id column to call the unpackId udf.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>  <span style="color:#f92672">.</span>withColumn<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;id&#34;</span><span style="color:#f92672">,</span> unpackId<span style="color:#f92672">(</span>substring<span style="color:#f92672">(</span>col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;value&#34;</span><span style="color:#f92672">),</span> <span style="color:#ae81ff">6</span><span style="color:#f92672">,</span> <span style="color:#ae81ff">7</span><span style="color:#f92672">)))</span>
</span></span></code></pre></div><p>Nesting functions like this is very powerful and useful. You can often save your self from creating lots of temporary columns by doing this. Though it can sometimes be harder to work out what is going on.</p>
<p>Our original use case was to be able to find the full date of the first observation of an object. So the next thing we need to extract is the date and time of the observation. This starts in column 16 and is 16 characters long. We will also need to create a function to convert the date from a string into an actual date object.</p>
<p>The date format is a little bit weird. The Year, month and day are pretty reasonable. The first four characters are the year, the next two the month and the next two are the day. The rest of the string however is the part of the day divided into 10000 chunks. So we need to work out how many seconds we have if we take the number of seconds in a day and divide it by 10000. Then we can multiply that number by the last part of the string. Take a look at the code below it will hopefully make a little more sense.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>  <span style="color:#66d9ef">def</span> dateFunc<span style="color:#f92672">(</span>in <span style="color:#66d9ef">:</span> <span style="color:#66d9ef">String</span><span style="color:#f92672">)</span><span style="color:#66d9ef">:</span> <span style="color:#66d9ef">Long</span> <span style="color:#f92672">=</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> year <span style="color:#66d9ef">=</span> in<span style="color:#f92672">.</span>substring<span style="color:#f92672">(</span><span style="color:#ae81ff">0</span><span style="color:#f92672">,</span> <span style="color:#ae81ff">4</span><span style="color:#f92672">).</span>toInt
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> month <span style="color:#66d9ef">=</span> in<span style="color:#f92672">.</span>substring<span style="color:#f92672">(</span><span style="color:#ae81ff">5</span><span style="color:#f92672">,</span> <span style="color:#ae81ff">7</span><span style="color:#f92672">).</span>toInt
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> day <span style="color:#66d9ef">=</span> in<span style="color:#f92672">.</span>substring<span style="color:#f92672">(</span><span style="color:#ae81ff">8</span><span style="color:#f92672">,</span> <span style="color:#ae81ff">10</span><span style="color:#f92672">).</span>toInt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> part <span style="color:#66d9ef">=</span> in<span style="color:#f92672">.</span>substring<span style="color:#f92672">(</span><span style="color:#ae81ff">11</span><span style="color:#f92672">).</span>replaceAll<span style="color:#f92672">(</span><span style="color:#e6db74">&#34; &#34;</span><span style="color:#f92672">,</span> <span style="color:#e6db74">&#34;0&#34;</span><span style="color:#f92672">).</span>toInt
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> seconds <span style="color:#66d9ef">=</span> <span style="color:#a6e22e">Math</span><span style="color:#f92672">.</span>round<span style="color:#f92672">(((</span><span style="color:#ae81ff">24</span><span style="color:#f92672">*</span><span style="color:#ae81ff">60</span><span style="color:#f92672">*</span><span style="color:#ae81ff">60</span><span style="color:#f92672">)</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">0.00001</span><span style="color:#f92672">)</span> <span style="color:#f92672">*</span> part<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">LocalDate</span><span style="color:#f92672">.</span>of<span style="color:#f92672">(</span>year<span style="color:#f92672">,</span> month<span style="color:#f92672">,</span> day<span style="color:#f92672">).</span>atStartOfDay<span style="color:#f92672">()</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">.</span>plus<span style="color:#f92672">(</span>seconds<span style="color:#f92672">,</span> <span style="color:#a6e22e">ChronoUnit</span><span style="color:#f92672">.</span><span style="color:#a6e22e">SECONDS</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">.</span>toInstant<span style="color:#f92672">(</span><span style="color:#a6e22e">ZoneOffset</span><span style="color:#f92672">.</span><span style="color:#a6e22e">UTC</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">.</span>getEpochSecond
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">}</span>
</span></span></code></pre></div><p>I&rsquo;ve used the java 8 date functions as they are a significant improvement over the older API. The bit to watch out for is to make sure you set the time zone to utc. Finally we return it in epoch seconds as spark doesn&rsquo;t know how to handle dates very well. It is just a lot easier to deal with the date a long. Now turn the <code>dateFunc</code> into a udf like we did with the <code>unpackIdFunc</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span> <span style="color:#66d9ef">val</span> dateConvert <span style="color:#66d9ef">=</span> udf<span style="color:#f92672">(</span><span style="color:#a6e22e">ObsUtils</span><span style="color:#f92672">.</span>dateFunc <span style="color:#66d9ef">_</span><span style="color:#f92672">)</span>
</span></span></code></pre></div><p>Then add another column to our data frame.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>  <span style="color:#f92672">.</span>withColumn<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;ts&#34;</span><span style="color:#f92672">,</span> dateConvert<span style="color:#f92672">(</span>substring<span style="color:#f92672">(</span>col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;value&#34;</span><span style="color:#f92672">),</span> <span style="color:#ae81ff">16</span><span style="color:#f92672">,</span> <span style="color:#ae81ff">16</span><span style="color:#f92672">)))</span>
</span></span></code></pre></div><p>We now have the columns we need so we can try and find the minimum time stamp for each id. To do this we need to first group by the id column and then find the minimum of the ts column. We will need to use the <code>.groupBy()</code> and <code>col</code> functions for the first part. Rather inconsistently the <code>.min()</code> function does not need its column name wrapped in a col call. What we end up with should look like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span><span style="color:#66d9ef">val</span> obs <span style="color:#66d9ef">=</span> spark<span style="color:#f92672">.</span>read<span style="color:#f92672">.</span>text<span style="color:#f92672">(</span>observationPath<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">.</span>withColumn<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;id&#34;</span><span style="color:#f92672">,</span> unpackId<span style="color:#f92672">(</span>substring<span style="color:#f92672">(</span>col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;value&#34;</span><span style="color:#f92672">),</span> idStart<span style="color:#f92672">,</span> idLen<span style="color:#f92672">)))</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">.</span>withColumn<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;ts&#34;</span><span style="color:#f92672">,</span> dateConvert<span style="color:#f92672">(</span>substring<span style="color:#f92672">(</span>col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;value&#34;</span><span style="color:#f92672">),</span> <span style="color:#ae81ff">16</span><span style="color:#f92672">,</span> <span style="color:#ae81ff">16</span><span style="color:#f92672">)))</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">.</span>groupBy<span style="color:#f92672">(</span>col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;id&#34;</span><span style="color:#f92672">))</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">.</span>min<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;ts&#34;</span><span style="color:#f92672">)</span>
</span></span></code></pre></div><p>There are other aggregation functions available as you would expect. There is a <code>max</code>, <code>avg</code>, and <code>sum</code> to get you started. It is also possible to create your own.</p>
<p>Now you can add a <code>obs.show(5)</code> call below it to have a look at the results you have. You should see two columns that look something like below</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>+-------+-----------+
</span></span><span style="display:flex;"><span>|     id|    min(ts)|
</span></span><span style="display:flex;"><span>+-------+-----------+
</span></span><span style="display:flex;"><span>|1908 OD|-1938820667|
</span></span><span style="display:flex;"><span>|1914 KA|-1755298440|
</span></span><span style="display:flex;"><span>|1927 UA|-1331164308|
</span></span><span style="display:flex;"><span>|1931 RS|-1208486322|
</span></span><span style="display:flex;"><span>|1933 DC|-1163208817|
</span></span><span style="display:flex;"><span>+-------+-----------+
</span></span><span style="display:flex;"><span>only showing top 5 rows
</span></span></code></pre></div><p>The dates are not massively useful like this. Converting them back in to a human readable string requires another user defined function.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>   <span style="color:#66d9ef">def</span> formatDateFunc<span style="color:#f92672">(</span>in <span style="color:#66d9ef">:</span> <span style="color:#66d9ef">Long</span><span style="color:#f92672">)</span> <span style="color:#66d9ef">:</span> <span style="color:#66d9ef">String</span> <span style="color:#f92672">=</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">LocalDateTime</span><span style="color:#f92672">.</span>ofEpochSecond<span style="color:#f92672">(</span>in<span style="color:#f92672">,</span> <span style="color:#ae81ff">0</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">ZoneOffset</span><span style="color:#f92672">.</span><span style="color:#a6e22e">UTC</span><span style="color:#f92672">).</span>format<span style="color:#f92672">(</span><span style="color:#a6e22e">DateTimeFormatter</span><span style="color:#f92672">.</span><span style="color:#a6e22e">ISO_LOCAL_DATE_TIME</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">}</span>
</span></span></code></pre></div><p>This uses the built in ISO8601 date formatter. We could use any thing but might as well use the built in standard. Turn it into a UDF as usual.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>  <span style="color:#66d9ef">val</span> formatDate <span style="color:#66d9ef">=</span> udf<span style="color:#f92672">(</span><span style="color:#a6e22e">ObsUtils</span><span style="color:#f92672">.</span>formatDateFunc <span style="color:#66d9ef">_</span><span style="color:#f92672">)</span>
</span></span></code></pre></div><p>Last we need to use this to format the ts column. We can use the <code>.withColumn()</code> function like before. But this time we are also going to use the <code>.select()</code> function to remove the min(ts) column that we don&rsquo;t need any more. This can be very useful if you only need a few columns in a large data set. Also note that we had to call the ts column min(ts) now as it has had its name changed by the min function.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>  <span style="color:#f92672">.</span>withColumn<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;date&#34;</span><span style="color:#f92672">,</span> formatDate<span style="color:#f92672">(</span>col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;min(ts)&#34;</span><span style="color:#f92672">)))</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">.</span>select<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;id&#34;</span><span style="color:#f92672">,</span> <span style="color:#e6db74">&#34;date&#34;</span><span style="color:#f92672">)</span>
</span></span></code></pre></div><p>If you now run <code>obs.show(5)</code> you should get something like</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>+-------+-------------------+
</span></span><span style="display:flex;"><span>|     id|               date|
</span></span><span style="display:flex;"><span>+-------+-------------------+
</span></span><span style="display:flex;"><span>|1908 OD|1908-07-24T22:42:13|
</span></span><span style="display:flex;"><span>|1914 KA|1914-05-19T01:06:00|
</span></span><span style="display:flex;"><span>|1927 UA|1927-10-27T00:08:12|
</span></span><span style="display:flex;"><span>|1931 RS|1931-09-15T21:21:18|
</span></span><span style="display:flex;"><span>|1933 DC|1933-02-20T22:26:23|
</span></span><span style="display:flex;"><span>+-------+-------------------+
</span></span><span style="display:flex;"><span>only showing top 5 rows
</span></span></code></pre></div><h1 id="conclusion">Conclusion</h1>
<p>In this post we have learnt to read a text file, pull the bits out of the lines that we need, create user defined functions to handle more complex processing of columns, group by columns, perform aggregations, and select columns. This will hopefully leave you in pretty good stead for processing data.  We will join up these two data sets in the next post.</p>
<p>If you found this interesting or you have any questions please let me know on twitter.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Spark and the Minor Planet Center data</title>
      <link>https://parsecsreach.org/post/spark_and_mpc/</link>
      <pubDate>Sat, 02 Dec 2017 08:55:24 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/spark_and_mpc/</guid>
      <description>Introduction A few weeks ago I saw comments between @Sondy and @JLGalache talking about getting a list of asteroids with their date of discovery. The main data file lists the year of discovery but not the actual date. I thought there was a way to get this information by looking at the observation file and joining it to the main data file. Todo this I decided to use Apache Spark. In this post I&amp;rsquo;ll go through setting up the spark environment and reading the json object file.</description>
      <content:encoded><![CDATA[<h1 id="introduction">Introduction</h1>
<p>A few weeks ago I saw comments between <a href="https://twitter.com/sondy">@Sondy</a> and <a href="https://twitter.com/JLGalache">@JLGalache</a> talking about getting a list of asteroids with their date of discovery. The main data file lists the year of discovery but not the actual date. I thought there was a way to get this information by looking at the observation file and joining it to the main data file. Todo this I decided to use Apache Spark. In this post I&rsquo;ll go through setting up the spark environment and reading the json object file.</p>
<h2 id="what-is-spark">What is spark?</h2>
<p><a href="https://spark.apache.org/">Spark</a> is an in-memory distributed processing framework. It is one of the largest open source data processing frameworks. There is a core section and several modules built on top. For this we will be using Spark SQL.</p>
<h2 id="what-is-the-minor-planet-center">What is the Minor Planet Center?</h2>
<p>The <a href="http://www.minorplanetcenter.net/iau/mpc.html">minor planet center</a> is an organization that keeps track of observations of asteroids. They keep a list of all the known asteroids and all the observations people around the world have made. They publish this data in a range of formats from a fixed width text file to json. Their documentation has improved a lot in the last couple of years.</p>
<h2 id="what-data-are-we-looking-at">What data are we looking at?</h2>
<p>A JSON file which contains information about all the known asteroids. This has their orbital parameters, id, name, discovery year, etc. Don&rsquo;t worry too much we won&rsquo;t get in to orbital mechanics here. (Mostly because I struggle with it my self)</p>
<p>While these data files are not big data by any means they are real data that has actual quirks and is not tiny which makes them a good thing to practice with. They shouldn&rsquo;t take hours to process.</p>
<h1 id="assumptions">Assumptions</h1>
<p>I am going to assume you have a local java installation and an IDE you are comfortable with. You know some scala. You can probably work out whats going on but it will be a lot easier if you are aware of the scala syntax before you start reading this.</p>
<h1 id="setup">Setup</h1>
<p>First we need to go and get the data files. These are reasonably big. On the minor planet center <a href="http://www.minorplanetcenter.net/data">data page</a> there is a link to the <code>mpcorb_extended.json.gz</code> file. Download this. This might take a while. So feel free to get started with the next bit.</p>
<p>I use <a href="https://www.jetbrains.com/idea/">intelliJ</a>. So create a new <a href="https://gradle.org/">gradle</a> project. There is no particular reason I chose Gradle over SBT or maven only I have more experience with it. The basics of the project setup are easy enough. First add a few bits to mark the project as a scala project and create variables for versions of things. This will save repeating your self if you need to change them in the future. The versions I used were simply the latest version of spark and its matching scala version when I did this. Open up the build.gradle file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>apply plugin <span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;scala&#39;</span> <span style="color:#75715e">// Adding the scala flag adds steps to the build process for the scala compiler
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>project<span style="color:#f92672">.</span><span style="color:#a6e22e">ext</span><span style="color:#f92672">.</span><span style="color:#a6e22e">scalaVersion</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;2.11.9&#39;</span>
</span></span><span style="display:flex;"><span>project<span style="color:#f92672">.</span><span style="color:#a6e22e">ext</span><span style="color:#f92672">.</span><span style="color:#a6e22e">sparkVersion</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;2.2.0&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>sourceCompatibility <span style="color:#f92672">=</span> <span style="color:#ae81ff">1.8</span> <span style="color:#75715e">// Use Java 1.8 compatibility. Mostly this is a hint for the IDE rather than the build.
</span></span></span></code></pre></div><p>Then add the following dependencies to the dependencies section of your gradle file, there should be a junit dependency there already.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>    compile <span style="color:#e6db74">&#34;org.scala-lang:scala-library:${project.ext.scalaVersion}&#34;</span>
</span></span><span style="display:flex;"><span>    compile <span style="color:#e6db74">&#34;org.scala-lang:scala-reflect:${project.ext.scalaVersion}&#34;</span>
</span></span><span style="display:flex;"><span>    compile <span style="color:#e6db74">&#34;org.scala-lang:scala-compiler:${project.ext.scalaVersion}&#34;</span>
</span></span><span style="display:flex;"><span>    compile <span style="color:#e6db74">&#34;org.apache.spark:spark-core_2.11:${project.ext.sparkVersion}&#34;</span>
</span></span><span style="display:flex;"><span>    compile <span style="color:#e6db74">&#34;org.apache.spark:spark-sql_2.11:${project.ext.sparkVersion}&#34;</span>
</span></span></code></pre></div><h1 id="development">Development</h1>
<p>Now we need some actual code. Create a file in the <code>src/main/scala/&lt;Your package here&gt;</code> directory called something like <code>orbitdates.scala</code></p>
<p>Create a main method:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span><span style="color:#66d9ef">object</span> <span style="color:#a6e22e">OrbitDates</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">def</span> main<span style="color:#f92672">(</span>args<span style="color:#66d9ef">:</span> <span style="color:#66d9ef">Array</span><span style="color:#f92672">[</span><span style="color:#66d9ef">String</span><span style="color:#f92672">])</span> <span style="color:#66d9ef">:</span> <span style="color:#66d9ef">Unit</span> <span style="color:#f92672">=</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Our code will go here
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></div><p>Note: with spark you must create a main method this way and not use the <code>extends App</code> style syntax because the objects will be serialised and sent to worker nodes by spark later on. You will get very strange errors due to the order that the generated main method does things.</p>
<p>It is probably worth adding a <code>println(&quot;hello world&quot;)</code> at this point and making sure the project builds and runs. <code>./gradlew build</code> from the command line or the gradle build button in your ide should run cleanly. There will probably be a lot of downloading to be done the first time.</p>
<p>The first step of a spark program is getting hold of the right kind of spark context or session.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> spark <span style="color:#66d9ef">=</span> <span style="color:#a6e22e">SparkSession</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">.</span>builder<span style="color:#f92672">()</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">.</span>master<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;local[2]&#34;</span><span style="color:#f92672">)</span> <span style="color:#75715e">// if you have more cores or a cluster turn this up.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>      <span style="color:#f92672">.</span>appName<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;Spark SQL join observations&#34;</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">.</span>getOrCreate<span style="color:#f92672">()</span>
</span></span></code></pre></div><p>As we want to use Spark SQL we need a spark session. This needs to be told where our spark &ldquo;cluster&rdquo; is. For this we are just using local with two threads. If you have a more powerful machine feel free to turn up the number of threads. If you are lucky enough to have an access to a cluster you should put the address of the &ldquo;master&rdquo; node here. (Note: the master/slave terminology that spark uses is horrible. Controller/worker would make more sense and be less offensive)</p>
<p>The <code>.appName()</code> is just a name for your app. It can be anything. If you are running in a cluster this is what shows up on the web front end.</p>
<p>The <code>.getOrCreate()</code> call at the end of the chain returns the spark context you have just setup. If for some reason your code already has an active spark context this will handle that as well.</p>
<p>To make that work you will also need to include</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> org.apache.spark.sql._
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> org.apache.spark.sql.catalyst.expressions.Substring
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> org.apache.spark.sql.functions._
</span></span></code></pre></div><p>The first one covers the <code>SparkSession</code>. The other two we will get to later.</p>
<p>To make things clearer later I added a mapping from the arguments simply so I didn&rsquo;t have to remember if the data file was args(0) or args(34)</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span><span style="color:#66d9ef">val</span> mpcobs <span style="color:#66d9ef">=</span> args<span style="color:#f92672">(</span><span style="color:#ae81ff">0</span><span style="color:#f92672">)</span> <span style="color:#75715e">// The json data file of objects
</span></span></span></code></pre></div><p>Now we have the spark session and something holding the path to our input files we can start trying to read them. First lets start with the json data file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span><span style="color:#66d9ef">val</span> orbRec <span style="color:#66d9ef">=</span> spark<span style="color:#f92672">.</span>read
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">.</span>option<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;multiLine&#34;</span><span style="color:#f92672">,</span> <span style="color:#66d9ef">true</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">.</span>json<span style="color:#f92672">(</span>mpcobs<span style="color:#f92672">)</span>
</span></span></code></pre></div><p>This will give us what is called a data frame in spark sql parlance. Basically its a lazy representation of the data file. Nothing will actually be done until we ask for something to be returned. <code>spark.read</code> contains methods for reading lots of different kinds of data sources. These data sources all have different options that can be applied to them. Here we ask to read a multi line json file. This option has only existed since spark 2.2. We need the multi line option due to the way the MPC json file is layed out.</p>
<p>This will give us access to the data for each object as a record. To see how the data looks you can use the <code>.show()</code> function. This will display a simple table of the data to stdout when the program is run. This is very useful for working out how your data looks. There is also <code>.describe(&quot;column name&quot;)</code> function which will give you statistical information about your column.</p>
<p>The next step is to sort out the number column and remove the brackets that are around it. After that we can find the correct column to use for the id.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>  <span style="color:#f92672">.</span>withColumn<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;number_formatted&#34;</span><span style="color:#f92672">,</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Column</span><span style="color:#f92672">(</span><span style="color:#a6e22e">Substring</span><span style="color:#f92672">(</span>col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;Number&#34;</span><span style="color:#f92672">).</span>expr<span style="color:#f92672">,</span> lit<span style="color:#f92672">(</span><span style="color:#ae81ff">2</span><span style="color:#f92672">).</span>expr<span style="color:#f92672">,</span> <span style="color:#f92672">(</span>length<span style="color:#f92672">(</span>col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;Number&#34;</span><span style="color:#f92672">))-</span><span style="color:#ae81ff">2</span><span style="color:#f92672">).</span>expr<span style="color:#f92672">)))</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">.</span>withColumn<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;id&#34;</span><span style="color:#f92672">,</span> coalesce<span style="color:#f92672">(</span>col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;number_formatted&#34;</span><span style="color:#f92672">),</span> col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;Principal_desig&#34;</span><span style="color:#f92672">)))</span>
</span></span></code></pre></div><p>The first line adds an extra column that does a substring of the original column &ldquo;Number&rdquo; removing the first and last two characters from the column.
The second line creates a new column called &ldquo;id&rdquo; which will contain the &ldquo;number_formatted&rdquo; column if it is not null or it will use the &ldquo;Principal_desig&rdquo; column. The <code>coalesce</code> function is very useful when you need to pick the first non null column from a list of options.</p>
<p>Now you should have a block that looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scala" data-lang="scala"><span style="display:flex;"><span>  <span style="color:#66d9ef">val</span> orbRec <span style="color:#66d9ef">=</span> spark<span style="color:#f92672">.</span>read
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">.</span>option<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;multiLine&#34;</span><span style="color:#f92672">,</span> <span style="color:#66d9ef">true</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">.</span>json<span style="color:#f92672">(</span>mpcobs<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">.</span>withColumn<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;number_formatted&#34;</span><span style="color:#f92672">,</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Column</span><span style="color:#f92672">(</span><span style="color:#a6e22e">Substring</span><span style="color:#f92672">(</span>col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;Number&#34;</span><span style="color:#f92672">).</span>expr<span style="color:#f92672">,</span> lit<span style="color:#f92672">(</span><span style="color:#ae81ff">2</span><span style="color:#f92672">).</span>expr<span style="color:#f92672">,</span> <span style="color:#f92672">(</span>length<span style="color:#f92672">(</span>col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;Number&#34;</span><span style="color:#f92672">))-</span><span style="color:#ae81ff">2</span><span style="color:#f92672">).</span>expr<span style="color:#f92672">)))</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">.</span>withColumn<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;id&#34;</span><span style="color:#f92672">,</span> coalesce<span style="color:#f92672">(</span>col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;number_formatted&#34;</span><span style="color:#f92672">),</span> col<span style="color:#f92672">(</span><span style="color:#e6db74">&#34;Principal_desig&#34;</span><span style="color:#f92672">)))</span>
</span></span></code></pre></div><p>This simple code will read in the file and create an &ldquo;id&rdquo; column. If you add a <code>orbRec.show(2)</code> on the end you should see a very long table like below. There will also be a load of logging of what spark is doing. It should end up in stderr rather than stdout. You can see the last two columns are the ones we added.</p>
<pre tabindex="0"><code class="language-table" data-lang="table">+-------------+----------+---------+--------+----------------------------------+---------+----+----+---------+----------+---------+--------+------+---------+-------+--------+------+---------------+--------------------------+----------+--------------+------------+--------+---------+---------------+----------+------------+---------------+---------+----------------+--------------+-------------+---+---------+---------+--------+----------+----+----------------+---+
|Aphelion_dist|Arc_length|Arc_years|Computer|Critical_list_numbered_object_flag|    Epoch|   G|   H|Hex_flags|  Last_obs|        M|NEO_flag|  Name|     Node|Num_obs|Num_opps|Number|One_km_NEO_flag|One_opposition_object_flag|Orbit_type|Orbital_period|Other_desigs|PHA_flag|     Peri|Perihelion_dist|Perturbers|Perturbers_2|Principal_desig|      Ref|Semilatus_rectum|Synodic_period|           Tp|  U|        a|        e|       i|         n| rms|number_formatted| id|
+-------------+----------+---------+--------+----------------------------------+---------+----+----+---------+----------+---------+--------+------+---------+-------+--------+------+---------------+--------------------------+----------+--------------+------------+--------+---------+---------------+----------+------------+---------------+---------+----------------+--------------+-------------+---+---------+---------+--------+----------+----+----------------+---+
|     2.976646|      null|1801-2017|MPCLINUX|                              null|2458000.5|0.12|3.34|     0000|2017-03-05|309.49412|    null| Ceres| 80.30888|   6672|     113|   (1)|           null|                      null|       MBA|     4.6037329|   [1943 XB]|    null| 73.02368|      2.5581728|       M-v|         30h|        A899 OF|MPO399990|       1.3757948|       1.27749|2458236.41089|  0|2.7674094|0.0756074|10.59322|0.21408881| 0.6|               1|  1|
|    3.4125514|      null|1821-2017|MPCLINUX|                              null|2458000.5|0.11|4.13|     0000|2017-10-05|291.65136|    null|Pallas|173.08718|   7910|     108|   (2)|           null|                      null|       MBA|     4.6179031|        null|    null|309.99154|       2.133619|       M-v|         28h|           null|MPO421624|        1.312813|     1.2764032|2458320.73644|  0|2.7730852|0.2305974|34.83792|0.21343186|0.58|               2|  2|
+-------------+----------+---------+--------+----------------------------------+---------+----+----+---------+----------+---------+--------+------+---------+-------+--------+------+---------------+--------------------------+----------+--------------+------------+--------+---------+---------------+----------+------------+---------------+---------+----------------+--------------+-------------+---+---------+---------+--------+----------+----+----------------+---+
only showing top 2 rows
</code></pre><p>If you get errors about array index out of bounds you may have forgotten to pass the parameter for where the data file is.</p>
<p>Now you can start to explore a bit. Find out the stats of the columns using the <code>.describe()</code> function. Play with the <code>.select()</code> function to limit the number of columns. The <code>.where()</code> function can be used to filter the data. You can use <code>.agg()</code> with <code>min()</code> and<code>max()</code> to aggregate data. The <code>.count()</code> function will return the number of rows in a data set.</p>
<h2 id="challenge">Challenge</h2>
<ul>
<li>How many asteroids were first observed in the year 2000?</li>
</ul>
<p>The first person to send me a tweet with the answer will get a virtual hi-five and some kudos.</p>
<h1 id="conclusion">Conclusion</h1>
<p>Here we are going to leave it for today. I&rsquo;m planning to do another couple of posts. One reading the observation data file and another on joining every thing up. I hope this is useful and you found it interesting. If you did or you have questions please let me know on twitter.</p>
<h1 id="thanks">Thanks</h1>
<p>Thanks to the Minor planet center for making this data freely available.</p>
<p>Thanks to Sondy and JL for the idea.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Introduction</title>
      <link>https://parsecsreach.org/post/introduction/</link>
      <pubDate>Sat, 02 Dec 2017 08:01:19 +0000</pubDate>
      
      <guid>https://parsecsreach.org/post/introduction/</guid>
      <description>Hello World I&amp;rsquo;m Emily Selwood. Every so often I get the urge to try and start a blog again. Here is iteration 235.
What do I do? I build systems for a living. Mostly data processing but I&amp;rsquo;ll happily get my hands dirty doing anything that needs to get done. I&amp;rsquo;ve worked many things from Unity and C# to C, Groovy, Go, Javascript, and Big data things like Accumulo and Spark.</description>
      <content:encoded><![CDATA[<h1 id="hello-world">Hello World</h1>
<p>I&rsquo;m Emily Selwood. Every so often I get the urge to try and start a blog again. Here is iteration 235.</p>
<h2 id="what-do-i-do">What do I do?</h2>
<p>I build systems for a living. Mostly data processing but I&rsquo;ll happily get my hands dirty doing anything that needs to get done. I&rsquo;ve worked many things from Unity and C# to C, Groovy, Go, Javascript, and Big data things like Accumulo and Spark.</p>
<h2 id="what-will-be-here">What will be here?</h2>
<p>I am planning to have posts about technical topics. I have ideas for the first couple. They may take me a while to build. Due to my love of space many of the examples will use space data sets. I&rsquo;ll try to keep every thing free to use.</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>About Me</title>
      <link>https://parsecsreach.org/about/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://parsecsreach.org/about/</guid>
      <description>Who am I? I&amp;rsquo;m Emily Selwood. A software engineer by trade, parent and wife, and a hobbyist woodworker and repairer.
//todo: fill this in&amp;hellip;</description>
      <content:encoded><![CDATA[<h1 id="who-am-i">Who am I?</h1>
<p>I&rsquo;m Emily Selwood. A software engineer by trade, parent and wife, and a hobbyist woodworker and repairer.</p>
<p>//todo: fill this in&hellip;</p>
]]></content:encoded>
    </item>

    

    <item>
      <title>Projects</title>
      <link>https://parsecsreach.org/projects/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://parsecsreach.org/projects/</guid>
      <description>Orb viewer A web based 3d visualization of asteroids in the solar system. Move around, and click on dots to find out more.
Circle Shape thing A web toy for creating patterns of shapes
Tiffhax A handy tool to view the internals of a tiff file. Useful when trying to build something that edits or works with tiff files on a byte level.</description>
      <content:encoded><![CDATA[<h2 id="orb-viewerhttpsparsecsreachorgorbviewer"><a href="https://parsecsreach.org/orbviewer">Orb viewer</a></h2>
<p>A web based 3d visualization of asteroids in the solar system. Move around, and click on dots to find out more.</p>
<p><img loading="lazy" src="/img/projects/Orbviewer.png" alt="A screen shot of orbviewer. Lots of little dots on a black background"  />
</p>
<h2 id="circle-shape-thinghttpsparsecsreachorgcircle_shapes"><a href="https://parsecsreach.org/circle_shapes">Circle Shape thing</a></h2>
<p>A web toy for creating patterns of shapes</p>
<p><img loading="lazy" src="/img/projects/CircleShapeThing.png" alt="A screen shot of the circle shape thing. Some geometric shapes on the left and a load of controls on the right"  />
</p>
<h2 id="tiffhaxhttpsgithubcomemilyselwoodtiffhax"><a href="https://github.com/emilyselwood/tiffhax">Tiffhax</a></h2>
<p>A handy tool to view the internals of a tiff file. Useful when trying to build something that edits or works with tiff files on a byte level.</p>
<p><img loading="lazy" src="https://github.com/emilyselwood/tiffhax/blob/master/screenshot.png?raw=true" alt="A screen shot of tiff hax, a html table with colourful bits highlighted"  />
</p>
]]></content:encoded>
    </item>

    
  </channel>
</rss>
