<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>con.cor.dance</title><link href="https://con.cor.dance/" rel="alternate"></link><link href="https://con.cor.dance/feeds/all.atom.xml" rel="self"></link><id>https://con.cor.dance/</id><updated>2025-04-16T00:00:00-05:00</updated><subtitle>a state of things being in harmony</subtitle><entry><title>Declarative Deployment of OwnTracks Frontend with NixOS</title><link href="https://con.cor.dance/declarative-deployment-of-owntracks-frontend-with-nixos.html" rel="alternate"></link><published>2025-04-16T00:00:00-05:00</published><updated>2025-04-16T00:00:00-05:00</updated><author><name>Ben Anhalt</name></author><id>tag:con.cor.dance,2025-04-16:/declarative-deployment-of-owntracks-frontend-with-nixos.html</id><summary type="html">&lt;p&gt;This a followup to my &lt;a href="owntracks-with-nixos-and-tailscale.html"&gt;previous
post&lt;/a&gt; about setting up
OwnTracks on a home server running NixOS. One step of that process was
to download the OwnTracks Frontend single page application zip archive
and copy it into &lt;code&gt;/var/www/html/owntracks&lt;/code&gt; to be served by Nginx. Now
I'd like to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This a followup to my &lt;a href="owntracks-with-nixos-and-tailscale.html"&gt;previous
post&lt;/a&gt; about setting up
OwnTracks on a home server running NixOS. One step of that process was
to download the OwnTracks Frontend single page application zip archive
and copy it into &lt;code&gt;/var/www/html/owntracks&lt;/code&gt; to be served by Nginx. Now
I'd like to look at replacing this procedure with a declarative
approach by defining the resource in the server's &lt;code&gt;configuration.nix&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;An Aside: Google Drops the Ball&lt;/h2&gt;
&lt;p&gt;It turns out my concerns about Google's Map Timeline service were
prescient. Last month, a "technical issue" resulted in the &lt;a href="https://arstechnica.com/gadgets/2025/03/oops-google-says-it-might-have-deleted-your-maps-timeline-data/"&gt;loss of
users' Timeline
data&lt;/a&gt;. Luckily,
I had the back-up-to-cloud option turned on and was able to recover my
Timeline history. Once I had done that, I &lt;a href="https://www.reddit.com/r/GoogleMaps/comments/1chlsst/comment/la6n0k2/"&gt;exported the
data&lt;/a&gt;
from the app for safekeeping. At some point I might try converting and
importing that data into OwnTracks.&lt;/p&gt;
&lt;h2&gt;Writing a Nix Derivation&lt;/h2&gt;
&lt;p&gt;The first step to the declarative deployment is to write a Nix
derivation that packages the Frontend application. The principled
thing to do would probably be referencing the source repo and building
the application as part of the derivation. But since this is a
Javascript project built using Vite, I was afraid doing that might be
complicated. Also, I am primarily interested in a reproducible &lt;em&gt;server
deployment&lt;/em&gt; rather than the reproducibility of the package itself. By
that logic I decided to base the derivation on the prebuilt artifact
from the repo's
&lt;a href="https://github.com/owntracks/frontend/releases"&gt;releases&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here the goal will be to write a &lt;code&gt;owntracks-frontend.nix&lt;/code&gt; derivation
and a &lt;code&gt;default.nix&lt;/code&gt; to invoke it so that executing &lt;code&gt;nix-build -A
owntrack-frontend&lt;/code&gt; generates a &lt;code&gt;result&lt;/code&gt; link to a Nix store path
containing the contents of the OwnTracks Frontend package. To do this
I followed the nix.dev tutorial on &lt;a href="https://nix.dev/tutorials/packaging-existing-software"&gt;packaging existing
software&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the first iteration I created two files with the following
contents.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# owntracks-frontend.nix&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  stdenv&lt;span class="p"&gt;,&lt;/span&gt;
  fetchzip
&lt;span class="p"&gt;}:&lt;/span&gt;
stdenv&lt;span class="o"&gt;.&lt;/span&gt;mkDerivation &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;pname =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;owntracks-frontend&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="ss"&gt;version =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;v2.15.3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="ss"&gt;src =&lt;/span&gt; fetchzip &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;url =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://github.com/owntracks/frontend/releases/download/v2.15.3/v2.15.3-dist.zip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;sha256 =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And,&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# default.nix&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt;
  &lt;span class="ss"&gt;pkgs =&lt;/span&gt; &lt;span class="nb"&gt;import&lt;/span&gt; &lt;span class="l"&gt;&amp;lt;nixpkgs&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;config =&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt; &lt;span class="ss"&gt;overlays =&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;in&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;owntracks-frontend =&lt;/span&gt; pkgs&lt;span class="o"&gt;.&lt;/span&gt;callPackage &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="l"&gt;/owntracks-frontend.nix&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then executing &lt;code&gt;nix-build -A owntracks-frontend&lt;/code&gt; produces the
expected error regarding the incorrect hash.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mismatch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fixed&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;derivation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/nix/store/im2lmhh4a2h7x87plz9i1fsc5fw8vhyf-source.drv&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;specified&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;iy&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;yISPcOD&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;lTyJUb1eI3wufLku1mKfVDm0&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;Dy8OKk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;derivation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/nix/store/bm08ivlk0nqc61kz6vallfndbq2xn5ly-owntracks-frontend-v2.15.3.drv&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This error gives the correct hash so it can be put into the
derivation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  &lt;span class="ss"&gt;src =&lt;/span&gt; fetchzip &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;url =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://github.com/owntracks/frontend/releases/download/v2.15.3/v2.15.3-dist.zip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;sha256 =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;iy+yISPcOD/2lTyJUb1eI3wufLku1mKfVDm0+Dy8OKk=&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, Nix will accept the source, and the build proceeds up to the next
error.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;/nix/store/csw1kq323p6lipcwbs9q109rf85vbx1n-owntracks-frontend-v2.15.3.drv&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;produce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;out&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;/nix/store/csw1kq323p6lipcwbs9q109rf85vbx1n-owntracks-frontend-v2.15.3.drv.chroot/root/nix/store/br9k7nxj067cmza64s5r92f2zchrj5vx-owntracks-frontend-v2.15.3&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This means that the derivation is not creating an output directory
which should contain the result. That's because this package isn't a
normal Autotools build. All that really needs to happen, with one
caveat I'll get to, is to copy the files that were downloaded as the
source into the output directory. That can be accomplished by
overriding the install phase as
&lt;a href="https://nix.dev/tutorials/packaging-existing-software#installphase"&gt;described&lt;/a&gt;
in the tutorial.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# owntracks-frontend.nix&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  stdenv&lt;span class="p"&gt;,&lt;/span&gt;
  fetchzip
&lt;span class="p"&gt;}:&lt;/span&gt;
stdenv&lt;span class="o"&gt;.&lt;/span&gt;mkDerivation &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;pname =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;owntracks-frontend&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="ss"&gt;version =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;v2.15.3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="ss"&gt;src =&lt;/span&gt; fetchzip &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;url =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://github.com/owntracks/frontend/releases/download/v2.15.3/v2.15.3-dist.zip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;sha256 =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;iy+yISPcOD/2lTyJUb1eI3wufLku1mKfVDm0+Dy8OKk=&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="ss"&gt;installPhase =&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;    runHook preInstall&lt;/span&gt;
&lt;span class="s1"&gt;    cp -r . $out&lt;/span&gt;
&lt;span class="s1"&gt;    runHook postInstall&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, &lt;code&gt;nix-build -A owntrack-frontend&lt;/code&gt; is able to succeed, and the
result link points to a Nix store path containing the expected files.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;nix-build&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;owntracks-frontend
&lt;span class="go"&gt;this derivation will be built:&lt;/span&gt;
&lt;span class="go"&gt;  /nix/store/n07d9lsy8g4h8q7b69i51511ddfnqpq7-owntracks-frontend-v2.15.3.drv&lt;/span&gt;
&lt;span class="go"&gt;building &amp;#39;/nix/store/n07d9lsy8g4h8q7b69i51511ddfnqpq7-owntracks-frontend-v2.15.3.drv&amp;#39;...&lt;/span&gt;
&lt;span class="go"&gt;Running phase: unpackPhase&lt;/span&gt;
&lt;span class="go"&gt;unpacking source archive /nix/store/nwca1gys4hl68cnslmiakcjaqkl8v612-source&lt;/span&gt;
&lt;span class="go"&gt;source root is source&lt;/span&gt;
&lt;span class="go"&gt;Running phase: patchPhase&lt;/span&gt;
&lt;span class="go"&gt;Running phase: updateAutotoolsGnuConfigScriptsPhase&lt;/span&gt;
&lt;span class="go"&gt;Running phase: configurePhase&lt;/span&gt;
&lt;span class="go"&gt;no configure script, doing nothing&lt;/span&gt;
&lt;span class="go"&gt;Running phase: buildPhase&lt;/span&gt;
&lt;span class="go"&gt;no Makefile or custom buildPhase, doing nothing&lt;/span&gt;
&lt;span class="go"&gt;Running phase: installPhase&lt;/span&gt;
&lt;span class="go"&gt;Running phase: fixupPhase&lt;/span&gt;
&lt;span class="go"&gt;shrinking RPATHs of ELF executables and libraries in /nix/store/4z9iardq8amf6f54azfznphy2dbb76fh-owntracks-frontend-v2.15.3&lt;/span&gt;
&lt;span class="go"&gt;checking for references to /build/ in /nix/store/4z9iardq8amf6f54azfznphy2dbb76fh-owntracks-frontend-v2.15.3...&lt;/span&gt;
&lt;span class="go"&gt;patching script interpreter paths in /nix/store/4z9iardq8amf6f54azfznphy2dbb76fh-owntracks-frontend-v2.15.3&lt;/span&gt;
&lt;span class="go"&gt;/nix/store/4z9iardq8amf6f54azfznphy2dbb76fh-owntracks-frontend-v2.15.3&lt;/span&gt;

&lt;span class="gp"&gt;$ &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-lL&lt;span class="w"&gt; &lt;/span&gt;result
&lt;span class="go"&gt;total 60&lt;/span&gt;
&lt;span class="go"&gt;dr-xr-xr-x 2 root root  4096 Dec 31  1969 assets&lt;/span&gt;
&lt;span class="go"&gt;dr-xr-xr-x 2 root root  4096 Dec 31  1969 config&lt;/span&gt;
&lt;span class="go"&gt;-r--r--r-- 1 root root  1150 Dec 31  1969 favicon.ico&lt;/span&gt;
&lt;span class="go"&gt;-r--r--r-- 1 root root  3647 Dec 31  1969 icon-180x180.png&lt;/span&gt;
&lt;span class="go"&gt;-r--r--r-- 1 root root   718 Dec 31  1969 index.html&lt;/span&gt;
&lt;span class="go"&gt;-r--r--r-- 1 root root   377 Dec 31  1969 manifest.json&lt;/span&gt;
&lt;span class="go"&gt;-r--r--r-- 1 root root 36117 Dec 31  1969 OwnTracks.svg&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The caveat I mentioned is that the &lt;code&gt;config&lt;/code&gt; directory needs to include
a &lt;code&gt;config.js&lt;/code&gt; file to set up the frontend app. As described in the
previous post, for my purposes it needs to contain the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owntracks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owntracks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owntracks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://wimpy.bleak-moth.ts.net:8083&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;basePath&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;owntracks&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This can be accomplished using the &lt;code&gt;pkgs.writeText&lt;/code&gt; library function
which puts some arbitrary text into a file in the Nix store. That file
can then be referenced later in the derivation. So, I added such an
attribute to the &lt;code&gt;mkDerivation&lt;/code&gt; call and then, in the install phase,
copied the generated file into the appropriate place in the &lt;code&gt;$out&lt;/code&gt;
directory. Notice, this entails adding &lt;code&gt;writeText&lt;/code&gt; to the argument
list of the function.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# owntracks-frontend.nix&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  stdenv&lt;span class="p"&gt;,&lt;/span&gt;
  writeText&lt;span class="p"&gt;,&lt;/span&gt;
  fetchzip
&lt;span class="p"&gt;}:&lt;/span&gt;
stdenv&lt;span class="o"&gt;.&lt;/span&gt;mkDerivation &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;pname =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;owntracks-frontend&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="ss"&gt;version =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;v2.15.3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="ss"&gt;src =&lt;/span&gt; fetchzip &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;url =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://github.com/owntracks/frontend/releases/download/v2.15.3/v2.15.3-dist.zip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;sha256 =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;iy+yISPcOD/2lTyJUb1eI3wufLku1mKfVDm0+Dy8OKk=&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="ss"&gt;config =&lt;/span&gt; writeText &lt;span class="s2"&gt;&amp;quot;config.js&amp;quot;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;    window.owntracks = window.owntracks || {};&lt;/span&gt;
&lt;span class="s1"&gt;    window.owntracks.config = {&lt;/span&gt;
&lt;span class="s1"&gt;      api: {&lt;/span&gt;
&lt;span class="s1"&gt;        baseUrl: &amp;quot;http://wimpy.bleak-moth.ts.net:8083&amp;quot;&lt;/span&gt;
&lt;span class="s1"&gt;      },&lt;/span&gt;
&lt;span class="s1"&gt;      router: {&lt;/span&gt;
&lt;span class="s1"&gt;        basePath: &amp;quot;owntracks&amp;quot;&lt;/span&gt;
&lt;span class="s1"&gt;      }&lt;/span&gt;
&lt;span class="s1"&gt;    };&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="ss"&gt;installPhase =&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;    runHook preInstall&lt;/span&gt;
&lt;span class="s1"&gt;    cp -r . $out&lt;/span&gt;
&lt;span class="s1"&gt;    cp $config $out/config/config.js&lt;/span&gt;
&lt;span class="s1"&gt;    runHook postInstall&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Rebuilding the package and checking its contents shows the &lt;code&gt;config.js&lt;/code&gt;
file is present and has the right content.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;nix-build&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;owntracks-frontend
&lt;span class="go"&gt;these 2 derivations will be built:&lt;/span&gt;
&lt;span class="go"&gt;  /nix/store/dacc8ma93fl60qvp359nqb0b9liv5v57-config.js.drv&lt;/span&gt;
&lt;span class="go"&gt;  /nix/store/adpvald53kxzhcfmm840xdvfydbn88yw-owntracks-frontend-v2.15.3.drv&lt;/span&gt;
&lt;span class="go"&gt;.&lt;/span&gt;
&lt;span class="go"&gt;.&lt;/span&gt;
&lt;span class="go"&gt;.&lt;/span&gt;

&lt;span class="go"&gt;/nix/store/dbml7xvlqv2lgcjfb3s0y17m5nj9bdmi-owntracks-frontend-v2.15.3&lt;/span&gt;

&lt;span class="gp"&gt;$ &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;result/config/config.js&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="go"&gt;window.owntracks = window.owntracks || {};&lt;/span&gt;
&lt;span class="go"&gt;window.owntracks.config = {&lt;/span&gt;
&lt;span class="go"&gt;  api: {&lt;/span&gt;
&lt;span class="go"&gt;    baseUrl: &amp;quot;http://wimpy.bleak-moth.ts.net:8083&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;  },&lt;/span&gt;
&lt;span class="go"&gt;  router: {&lt;/span&gt;
&lt;span class="go"&gt;    basePath: &amp;quot;owntracks&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;  }&lt;/span&gt;
&lt;span class="go"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Making Use of the Derivation&lt;/h2&gt;
&lt;p&gt;At this point I'm able to build a directory in the Nix store which
contains the SPA that I want to serve with Nginx. If done manually
this would involve updating the Nginx configuration with a &lt;em&gt;location&lt;/em&gt;
block pointing to the path in the Nix store. Obviously, I want to
accomplish this declaratively through &lt;code&gt;configuration.nix&lt;/code&gt; in my NixOS
server. &lt;/p&gt;
&lt;p&gt;I wasn't immediately sure how to go about this. I knew that I could
refer to derivations in &lt;code&gt;pkgs&lt;/code&gt; using string interpolation and get the
path to a package in the Nix store. I knew Nix would make sure the
package was present in the store and build or download it if
necessary.  So if a &lt;code&gt;pkgs.owntracks-frontend&lt;/code&gt; existed, I could do
something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  services&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;nginx =&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;enable =&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      virtualHosts&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;wimpy&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;root =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/var/www/html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        locations&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/owntracks/&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;alias =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;owntracks-frontend&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Obviously, there is no &lt;code&gt;pkgs.owntracks-fronted&lt;/code&gt;. But, I do now have
&lt;code&gt;owntracks-frontend.nix&lt;/code&gt; which contains a function that will produce
the same kind of derivation as those in &lt;code&gt;pkgs&lt;/code&gt; if invoked correctly. I
reasoned that that is what &lt;code&gt;default.nix&lt;/code&gt; is doing with the
&lt;code&gt;pkgs.callPackage&lt;/code&gt; invocation, and so maybe I could just do the same
thing in my &lt;code&gt;configuration.nix&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I copied &lt;code&gt;owntracks-frontend.nix&lt;/code&gt; to be next to &lt;code&gt;configuration.nix&lt;/code&gt; in
my NixOS config repo and updated the latter with the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  services&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;nginx =&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt;
      &lt;span class="ss"&gt;owntracks-frontend =&lt;/span&gt; pkgs&lt;span class="o"&gt;.&lt;/span&gt;callPackage &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="l"&gt;/owntracks-frontend.nix&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;enable =&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      virtualHosts&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;wimpy&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;root =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/var/www/html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        locations&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/owntracks/&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;alias =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;owntracks-frontend&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And, it worked!&lt;/p&gt;
&lt;h2&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;I feel pretty happy with this accomplishment. It was really pretty
simple, but I feel like I'm starting to get an inkling of how things
in NixOS work.&lt;/p&gt;
&lt;p&gt;One improvement that could be made, I think, is parameterizing the
values in the &lt;code&gt;config.js&lt;/code&gt; file. Right now the &lt;code&gt;/owntracks/&lt;/code&gt; base path
is duplicated in the Nginx config and &lt;code&gt;config.js&lt;/code&gt;. And surfacing the base URL
for the API in the &lt;code&gt;configuration.nix&lt;/code&gt; would also make more
sense. Presumably, this could be accomplished by passing the values as
parameters to the function in &lt;code&gt;owntracks-frontend.nix&lt;/code&gt;. But I'll save
that for later.&lt;/p&gt;</content><category term="self-hosting"></category><category term="nixos"></category><category term="owntracks"></category></entry><entry><title>OwnTracks with NixOS and Tailscale</title><link href="https://con.cor.dance/owntracks-with-nixos-and-tailscale.html" rel="alternate"></link><published>2025-02-21T00:00:00-06:00</published><updated>2025-02-21T00:00:00-06:00</updated><author><name>Ben Anhalt</name></author><id>tag:con.cor.dance,2025-02-21:/owntracks-with-nixos-and-tailscale.html</id><summary type="html">&lt;p&gt;In an effort to reduce my reliance on big tech I recently decided to
try using &lt;a href="https://owntracks.org/"&gt;OwnTracks&lt;/a&gt; to replace Google Maps
Timeline. This article provides a rundown of how I accomplished this
using an existing home server running NixOS and my existing Tailnet.&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;For various reasons I find it …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In an effort to reduce my reliance on big tech I recently decided to
try using &lt;a href="https://owntracks.org/"&gt;OwnTracks&lt;/a&gt; to replace Google Maps
Timeline. This article provides a rundown of how I accomplished this
using an existing home server running NixOS and my existing Tailnet.&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;For various reasons I find it useful to be able to review my past
whereabouts, and Google Timeline has worked fairly well for this
purpose. At some point Google changed Timeline to only store location
history on the mobile device, removing it from the Maps web app. I've
also found that the UI for Timeline on my phone has become laggy and
feels like it could fail at any time. These seem like warning signs
that Google may be neglecting the service or that it may be dropped
entirely.&lt;/p&gt;
&lt;p&gt;To hedge against that possibility and in furtherance of my general
goal of reducing dependence on big tech, I've decided try replacing
this service with OwnTracks.&lt;/p&gt;
&lt;h2&gt;OwnTracks&lt;/h2&gt;
&lt;p&gt;OwnTracks consists of a few components, including mobile apps for
Android and iOS that send location updates to a server. The server can
either be an MQTT broker or the OwnTracks Recorder. Using an MQTT
broker permits live location sharing among devices running the
app. Since I only care about recording my location history, I can
forgo the MQTT broker and set up the app to publish updates directly
to the recorder via HTTP.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://owntracks.org/booklet/clients/recorder/"&gt;OwnTracks
Recorder&lt;/a&gt; acts as a
database to store location histories published by the mobile apps. It
also provides APIs for querying the stored histories.&lt;/p&gt;
&lt;p&gt;There is also an &lt;a href="https://github.com/owntracks/frontend"&gt;OwnTracks
Frontend&lt;/a&gt; which provides a web
app for viewing the stored location histories from the Recorder
service.&lt;/p&gt;
&lt;h2&gt;Tailscale&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://tailscale.com/"&gt;Tailscale&lt;/a&gt; is a peer-to-peer VPN
solution. Since I already have Tailscale set up on my home server and
my phone, I can avoid a lot of fiddly work securing (in both senses of
the word) a publicly available IP address for my server. As long as my
phone is connected to my Tailscale VPN, known as a Tailnet, my phone
can directly connect to the server.&lt;/p&gt;
&lt;h2&gt;Running the OwnTracks Recorder&lt;/h2&gt;
&lt;p&gt;I added a systemd service declaration to my NixOS configuration on my
server to run the OwnTracks Recorder service. To avoid running the
service with root privileges I'm using the &lt;a href="https://noise.getoto.net/2017/10/06/dynamic-users-with-systemd/"&gt;dynamic
user&lt;/a&gt;
facility provided by systemd which automatically creates an
unprivileged user to run the process. The &lt;code&gt;StateDirectory&lt;/code&gt; directive
causes a directory to be created under &lt;code&gt;/var/lib&lt;/code&gt; with permissions
allowing the service to store its persistent state (the location
histories, in this case) across restarts.&lt;/p&gt;
&lt;p&gt;Since OwnTracks Recorder has been &lt;a href="https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/by-name/ow/owntracks-recorder/package.nix"&gt;packaged for
NixOS&lt;/a&gt;
I can reference the package directly to invoke the service, and Nix
will automatically make sure it is installed. I'm using the unstable
channel so the package includes &lt;a href="https://github.com/NixOS/nixpkgs/commit/7ebad821473c78c7cfdf17ecf9f9be91d2455d81"&gt;a recent
commit&lt;/a&gt;
which fixes the HTTP mode for the service. This is necessary for the
setup to work. The fix should be included in the next stable release
of NixOS.&lt;/p&gt;
&lt;p&gt;When the executable is invoked it is provided with the path to the
dynamic user's state directory via the &lt;code&gt;--storage&lt;/code&gt; flag. The &lt;code&gt;--port
0&lt;/code&gt; flag tells Recorder not to try to connect to an MQTT broker. I
leave the HTTP port at the default 8083. Other command line options
are in the
&lt;a href="https://github.com/owntracks/recorder?tab=readme-ov-file#ot-recorder-options"&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  systemd&lt;span class="o"&gt;.&lt;/span&gt;services&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;owntracks =&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;enable =&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;description =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;owntracks recorder&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;serviceConfig =&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;ExecStart =&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;owntracks-recorder&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;/bin/ot-recorder \&lt;/span&gt;
&lt;span class="s1"&gt;           --storage /var/lib/owntracks/recorder/store \&lt;/span&gt;
&lt;span class="s1"&gt;           --port 0&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="ss"&gt;DynamicUser =&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="ss"&gt;StateDirectory =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;owntracks&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="ss"&gt;Restart =&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;always&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="ss"&gt;wantedBy =&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;multi-user.target&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Running &lt;code&gt;sudo nixos-rebuild switch&lt;/code&gt; will start the Recorder
service. This can be verified by visiting the HTTP endpoint in a
browser. Within my Tailnet my server is reachable at
&lt;code&gt;wimpy.bleak-moth.ts.net&lt;/code&gt;. Since the Recorder service is using its
default port, I can confirm it is running and available from my
Tailscale connected laptop or phone by visiting
&lt;code&gt;http://wimpy.bleak-moth.ts.net:8083&lt;/code&gt;. There I will see the minimal
web page the server provides:&lt;/p&gt;
&lt;p&gt;&lt;img alt="OwnTracks Recorder web page" src="images/owntracks-recorder-page.png"&gt;&lt;/p&gt;
&lt;h2&gt;Setting Up the Mobile App&lt;/h2&gt;
&lt;p&gt;The OwnTracks mobile app can be installed from the Google or Apple app
stores. It needs to be configured to send updates to the recorder by
setting the &lt;strong&gt;Mode&lt;/strong&gt; and &lt;strong&gt;Endpoint URL&lt;/strong&gt; values in the app's
&lt;em&gt;Preferences -&amp;gt; Connection&lt;/em&gt; menu. The mode is set to &lt;code&gt;HTTP&lt;/code&gt; since I'm
not using MQTT. The endpoint URL is set to
&lt;code&gt;http://wimpy.bleak-moth.ts.net:8083/pub&lt;/code&gt;. This is my server's FQDN
plus the path for publishing updates. I was stuck for awhile due to
not knowing the &lt;code&gt;/pub&lt;/code&gt; path was required.&lt;/p&gt;
&lt;p&gt;&lt;img alt="OwnTracks app connection
settings" src="images/owntracks-connection.png" width="200"&gt;&lt;/p&gt;
&lt;p&gt;As seen above, the &lt;em&gt;Connection&lt;/em&gt; settings also include
&lt;strong&gt;Identification&lt;/strong&gt; and &lt;strong&gt;Credentials&lt;/strong&gt; sections. Since the VPN
prevents anyone else from accessing the server, I am not setting up
any sort of access control in the OwnTracks system. Thus the
credentials do not need to be set, but it is worth setting a user name
so that the stored location history will be recorded under that
name. This name will then appear on the maps. Likewise, the values for
&lt;strong&gt;Device ID&lt;/strong&gt; and &lt;strong&gt;Tracker ID&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;With the &lt;em&gt;Connection&lt;/em&gt; settings configured, the mobile app should start
sending location updates to the server. Updates can be manually pushed
by clicking on the upload button in the map header of the app's main
view.&lt;/p&gt;
&lt;p&gt;&lt;img alt="OwnTracks app main map view" src="images/owntracks-map-view.png" width="200"&gt;&lt;/p&gt;
&lt;p&gt;I'm able to confirm the server is receiving location updates by
checking the logs for the OwnTracks Recorder service. Notice the POST
requests to the &lt;code&gt;/pub&lt;/code&gt; endpoint. These correspond to pushing the
upload button in the app on my phone.&lt;/p&gt;
&lt;p&gt;&lt;img alt="OwnTracks Recorder logs" src="images/owntracks-logs.png"&gt;&lt;/p&gt;
&lt;p&gt;I can also check that locations have been uploaded by opening the
location table link from the server's main web page.&lt;/p&gt;
&lt;p&gt;&lt;img alt="OwnTracks Recorder Location
Table" src="images/owntracks-recorder-location-table.png"&gt;&lt;/p&gt;
&lt;p&gt;For troubleshooting purposes, the mobile app provides a &lt;em&gt;Status&lt;/em&gt; menu
with useful information including logs. I found it helpful in getting
the connection established. I think the logs were what clued me into
the &lt;code&gt;/pub&lt;/code&gt; path.&lt;/p&gt;
&lt;h2&gt;Setting Up the Frontend&lt;/h2&gt;
&lt;p&gt;Once I had my location updates being recorded, it was time to set up
the frontend so that I could view the history. The &lt;a href="https://github.com/owntracks/frontend"&gt;OwnTracks
Frontend&lt;/a&gt; is a single page
application that can be served from any webserver. Since I already had
Nginx running and serving static pages out of &lt;code&gt;/var/www/html/&lt;/code&gt;, it was
pretty easy.&lt;/p&gt;
&lt;p&gt;The
&lt;a href="https://github.com/owntracks/frontend/blob/main/README.md"&gt;README&lt;/a&gt;
offers instructions to either use Docker or build the app manually,
but I just download the zip archive from the repo's
&lt;a href="https://github.com/owntracks/frontend/releases"&gt;releases&lt;/a&gt;
page. Inside, in typical NPM package fashion, is a &lt;code&gt;dist&lt;/code&gt; directory. I
copied the contents to &lt;code&gt;/var/www/html/owntracks&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The app is configured by copying the &lt;code&gt;config/config.example.js&lt;/code&gt; file
to &lt;code&gt;config/config.js&lt;/code&gt; and making edits there. For this setup I needed
to set two values. The &lt;code&gt;api.baseURL&lt;/code&gt; for the OwnTracks Recorder's API
is set to &lt;code&gt;http://wimpy.bleak-moth.ts.net:8083&lt;/code&gt;. And &lt;code&gt;router.basePath&lt;/code&gt;
is set to &lt;code&gt;owntracks&lt;/code&gt; since I'm hosting the app under that path. If
&lt;code&gt;router.basePath&lt;/code&gt; is not set correctly, the app will load but not
display the map. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Here you can overwite the default configuration values&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owntracks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owntracks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owntracks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://wimpy.bleak-moth.ts.net:8083&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;basePath&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;owntracks&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The working frontend looks like the image below and provides options
to view location history for past date-time ranges and with
options for points, lines, and a heatmap. Here I have zoomed the map
way out to avoid advertising my exact location to the Internet.&lt;/p&gt;
&lt;p&gt;&lt;img alt="OwnTracks Frontend" src="images/owntracks-frontend.png"&gt;&lt;/p&gt;
&lt;p&gt;My use case mostly involves viewing location histories for specific
days. At first I was confused by the UI for selecting dates. The trick
is that you have to select the date range by clicking on the start day
in the calendar and then the end day. If you want just a single day,
you have to click on that day &lt;em&gt;twice&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="OwnTracks Frontend date picker" src="images/owntracks-frontend-date-picker.png"&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;At this point I have a setup which seems to be working well enough for
my purposes. I've been using it for a few weeks, and I'm able to look
back over my history to see where I was at particular times. There are
a couple of things I would still like to do. First, it seems
like I should be able to write an ad-hoc Nix derivation for the
OwnTracks Frontend so that I can include it declaratively in my NixOS
configuration. This would be preferable to manually downloading and
copying the distribution. Secondly, I'd like my data to be backed
up. I think this should be as simple as setting up
&lt;a href="https://www.borgbackup.org/"&gt;Borg&lt;/a&gt; and pointing it at the OwnTracks
Recorder's state directory.&lt;/p&gt;</content><category term="self-hosting"></category><category term="nixos"></category><category term="owntracks"></category><category term="tailscale"></category><category term="degoogle"></category></entry><entry><title>Z3bra Puzzle</title><link href="https://con.cor.dance/z3bra-puzzle.html" rel="alternate"></link><published>2024-06-06T00:00:00-05:00</published><updated>2024-06-06T00:00:00-05:00</updated><author><name>Ben Anhalt</name></author><id>tag:con.cor.dance,2024-06-06:/z3bra-puzzle.html</id><summary type="html">&lt;p&gt;I occasionally encounter problems akin to &lt;a href="https://en.wikipedia.org/wiki/Logic_puzzle#Logic_grid_puzzles"&gt;logic grid
puzzles&lt;/a&gt;
in programming challenges or other contexts. I usually approach them
through some ad hoc backtracking algorithm. Recently I decided to try
and develop a systematic approach for solving such problems without
reinventing the wheel each time.&lt;/p&gt;
&lt;p&gt;These puzzles fall into the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I occasionally encounter problems akin to &lt;a href="https://en.wikipedia.org/wiki/Logic_puzzle#Logic_grid_puzzles"&gt;logic grid
puzzles&lt;/a&gt;
in programming challenges or other contexts. I usually approach them
through some ad hoc backtracking algorithm. Recently I decided to try
and develop a systematic approach for solving such problems without
reinventing the wheel each time.&lt;/p&gt;
&lt;p&gt;These puzzles fall into the category of &lt;a href="https://en.wikipedia.org/wiki/Constraint_satisfaction_problem"&gt;constraint satisfaction
problems&lt;/a&gt;. I
knew that tools like the
&lt;a href="https://en.wikipedia.org/wiki/Satisfiability_modulo_theories"&gt;SMT&lt;/a&gt;
solver, &lt;a href="https://en.wikipedia.org/wiki/Z3_Theorem_Prover"&gt;Z3&lt;/a&gt;, could
be used to solve such problems, but it wasn't immediately obvious to
me how to go about it.&lt;/p&gt;
&lt;p&gt;I encountered some difficulty finding examples or tutorials on
this. But, I did find &lt;a href="https://davidsherenowitsa.party/2018/09/19/solving-logic-puzzles-with-z3.html"&gt;this
post&lt;/a&gt;
by David Cook which, along with &lt;a href="https://gist.github.com/divergentdave/13a2a557c26146fc3e3b15a398f8428b"&gt;the associated
code&lt;/a&gt;,
was very helpful in getting started.&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;First of all, what are the characteristics of logic grid puzzles, and
how can they be described in a way that facilitates a systematic
approach to solving them?&lt;/p&gt;
&lt;p&gt;Broadly speaking, a logic grid puzzle can be characterized as a set of
dimensions, each of which has a number of discreet values. If the
puzzle is solvable, there are unique correspondences between the
values of each of the dimensions. The goal is to explicitly determine
the correspondences given a list of clues amounting to assertions that
most hold.&lt;/p&gt;
&lt;p&gt;The pen and paper approach involves representing the puzzle as a grid
and proceeds by marking correspondences as either established or
excluded using check marks and cross marks. For an SMT solution, a
means of encoding the puzzle using the abstractions provided by the
SMT solver must be sought.&lt;/p&gt;
&lt;h2&gt;The Zebra Puzzle&lt;/h2&gt;
&lt;p&gt;A classic example of this sort of puzzle is the &lt;a href="https://en.wikipedia.org/wiki/Zebra_Puzzle"&gt;Zebra
Puzzle&lt;/a&gt;. Quoting from the
Wikipedia article:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The following version of the puzzle appeared in Life International in 1962:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;There are five houses.&lt;/li&gt;
&lt;li&gt;The Englishman lives in the red house.&lt;/li&gt;
&lt;li&gt;The Spaniard owns the dog.&lt;/li&gt;
&lt;li&gt;Coffee is drunk in the green house.&lt;/li&gt;
&lt;li&gt;The Ukrainian drinks tea.&lt;/li&gt;
&lt;li&gt;The green house is immediately to the right of the ivory house.&lt;/li&gt;
&lt;li&gt;The Old Gold smoker owns snails.&lt;/li&gt;
&lt;li&gt;Kools are smoked in the yellow house.&lt;/li&gt;
&lt;li&gt;Milk is drunk in the middle house.&lt;/li&gt;
&lt;li&gt;The Norwegian lives in the first house.&lt;/li&gt;
&lt;li&gt;The man who smokes Chesterfields lives in the house next to the man with the fox.&lt;/li&gt;
&lt;li&gt;Kools are smoked in the house next to the house where the horse is kept.&lt;/li&gt;
&lt;li&gt;The Lucky Strike smoker drinks orange juice.&lt;/li&gt;
&lt;li&gt;The Japanese smokes Parliaments.&lt;/li&gt;
&lt;li&gt;The Norwegian lives next to the blue house.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, who drinks water? Who owns the zebra?&lt;/p&gt;
&lt;p&gt;In the interest of clarity, it must be added that each of the
   five houses is painted a different color, and their inhabitants
   are of different national extractions, own different pets, drink
   different beverages and smoke different brands of American
   cigarets [sic]. One other thing: in statement 6, right means
   your right.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Z3 Sorts&lt;/h3&gt;
&lt;p&gt;In this puzzle there are five dimensions. The colors of the houses,
the nationality of their inhabitants, the species of pets in the
houses, and the beverage and cigarette preferences of the
inhabitants. In Python Z3 each of these dimensions can be expressed as
an &lt;strong&gt;&lt;em&gt;EnumSort&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;House&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;red_house&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;green_house&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ivory_house&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yellow_house&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blue_house&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; \
    &lt;span class="n"&gt;EnumSort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;House&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;red green ivory yellow blue&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;Nation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;england&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ukraine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;norway&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;japan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; \
    &lt;span class="n"&gt;EnumSort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Nation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;england spain ukraine norway japan&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;Beverage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coffee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tea&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;milk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;juice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;water&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; \
    &lt;span class="n"&gt;EnumSort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Drink&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;coffee tea milk juice water&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;Pet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;snails&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;horse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zebra&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; \
    &lt;span class="n"&gt;EnumSort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Pet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;dog snails fox horse zebra&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;Smoke&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oldgold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chesterfields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;luckystrikes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parliaments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; \
    &lt;span class="n"&gt;EnumSort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Smoke&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;oldgold kools chesterfields luckystrikes parliaments&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;An &lt;strong&gt;&lt;em&gt;EnumSort&lt;/em&gt;&lt;/strong&gt; defines a new Z3 &lt;strong&gt;&lt;em&gt;sort&lt;/em&gt;&lt;/strong&gt;, similar to a type,
consisting of the specific given values and returns references to the
sort itself and constants for each of the values. The arguments are
the name of the sort in Z3 and the names of the values.&lt;/p&gt;
&lt;h3&gt;Z3 Function Declarations&lt;/h3&gt;
&lt;p&gt;The puzzle makes clear that correspondences amongst the dimensions are
all one-to-one.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the interest of clarity, it must be added that each of the five
houses is painted a different color, and their inhabitants are of
different national extractions, own different pets, drink different
beverages and smoke different brands of American cigarets [sic].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This means any of the dimensions could be thought of as a function of
any of the others. For this approach, however, it is useful to pick
one dimension as an independent variable and treat the other
dimensions as dependent variables that are functions of it.&lt;/p&gt;
&lt;p&gt;Many of the clues are stated in terms of the color of the houses, so
I'll use that as the independent variable. That's why I've used colors
as the values of the &lt;strong&gt;&lt;em&gt;House&lt;/em&gt;&lt;/strong&gt; sort. For each of the other
dimensions, a function is declared from the &lt;strong&gt;&lt;em&gt;House&lt;/em&gt;&lt;/strong&gt; sort to the
sort corresponding to that dimension. The &lt;strong&gt;&lt;em&gt;Function&lt;/em&gt;&lt;/strong&gt; constructor
takes the name of the function, the domain sort, and the codomain sort
as arguments.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;nationality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nationality&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;House&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Nation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;drinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;drinks&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;House&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Beverage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;pet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;House&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;smokes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;smokes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;House&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Smoke&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Some of the clues involve the ordering of the houses. This is handled
by declaring a function from the &lt;strong&gt;&lt;em&gt;House&lt;/em&gt;&lt;/strong&gt; sort to the integers,
&lt;strong&gt;&lt;em&gt;IntSort&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;house_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;house_number&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;House&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntSort&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Constraints&lt;/h3&gt;
&lt;p&gt;With the sorts and functions declared, the constraints of the problem
can be added as assertions. This is done by creating a &lt;strong&gt;&lt;em&gt;Solver&lt;/em&gt;&lt;/strong&gt;
instance and using the &lt;strong&gt;&lt;em&gt;add&lt;/em&gt;&lt;/strong&gt; method which can accept multiple
assertions as arguments.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;solver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Solver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The constraints will come from the clues and from the requirement that
the functions be one-to-one. To start with a simple assertion, clue 2
states, "The Englishman lives in the red house." This can be directly
translated into a Z3 constraint by asserting that the
&lt;strong&gt;&lt;em&gt;nationality&lt;/em&gt;&lt;/strong&gt; function evaluated at the &lt;strong&gt;&lt;em&gt;red_house&lt;/em&gt;&lt;/strong&gt; value of
the &lt;strong&gt;&lt;em&gt;House&lt;/em&gt;&lt;/strong&gt; sort must give the &lt;strong&gt;&lt;em&gt;england&lt;/em&gt;&lt;/strong&gt; value of the
&lt;strong&gt;&lt;em&gt;Nation&lt;/em&gt;&lt;/strong&gt; sort.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# 2. The Englishman lives in the red house.&lt;/span&gt;
    &lt;span class="n"&gt;nationality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;red_house&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;england&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Clues 4 and 8 are also straightforward since they involve statements
about specific colors of houses, the independent variable.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# 4. Coffee is drunk in the green house.&lt;/span&gt;
    &lt;span class="n"&gt;drinks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;green_house&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;coffee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;# 8. Kools are smoked in the yellow house.&lt;/span&gt;
    &lt;span class="n"&gt;smokes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yellow_house&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;kools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Relationships between two dependent variables&lt;/h4&gt;
&lt;p&gt;When a clue involves a relationship between two dependent variables,
the situation is a bit more complicated. Clue 3, "The Spaniard owns
the dog," is an example. There is no function from &lt;strong&gt;&lt;em&gt;Nation&lt;/em&gt;&lt;/strong&gt; to
&lt;strong&gt;&lt;em&gt;Pet&lt;/em&gt;&lt;/strong&gt; or vice-versa to characterize this statement. Nor is there
is an inverse from either to the independent variable that would
permit &lt;code&gt;pet(nationality_inverse(spain)) == dog&lt;/code&gt;. Instead, a universal
quantifier can be used to introduce a variable, &lt;strong&gt;&lt;em&gt;h&lt;/em&gt;&lt;/strong&gt;, of the
&lt;strong&gt;&lt;em&gt;House&lt;/em&gt;&lt;/strong&gt; sort. Now it can be asserted that anytime &lt;code&gt;nationality(h)&lt;/code&gt;
is &lt;strong&gt;&lt;em&gt;spain&lt;/em&gt;&lt;/strong&gt;, &lt;code&gt;pet(h)&lt;/code&gt; must be &lt;strong&gt;&lt;em&gt;dog&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The universal quantifier takes the form &lt;code&gt;ForAll(vars, assertion)&lt;/code&gt;,
where &lt;strong&gt;&lt;em&gt;vars&lt;/em&gt;&lt;/strong&gt; can be single variable or a list of variables. Owing to
how the language is represented in Python, each variable has to be
represented using a predefined constant to specify the sort. This
is a bit confusing since the variable inside the quantifier bears no
relationship to the constant other than the sort. With this in mind,
clue 3 can be encoded as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;h&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;House&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# A dummy constant.&lt;/span&gt;
&lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# 3. The Spaniard owns the dog.&lt;/span&gt;
    &lt;span class="n"&gt;ForAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nationality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;spain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The expressions &lt;code&gt;nationality(h) == spain&lt;/code&gt; and &lt;code&gt;pet(h) == dog&lt;/code&gt; both
evaluate to values of &lt;strong&gt;&lt;em&gt;BoolSort&lt;/em&gt;&lt;/strong&gt;, either true or false. By equating
them, the quantifier requires both to evaluate to the same truth value
for a given &lt;strong&gt;&lt;em&gt;h&lt;/em&gt;&lt;/strong&gt;. Meaning, for any given house "the occupant is
Spanish" is equivalent to "the house has a dog".  For clarity I think
it is worth defining a helper function for logical equivalence.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;Iff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This provides a nice parallel with the provided &lt;strong&gt;&lt;em&gt;Implies&lt;/em&gt;&lt;/strong&gt; function
that will be used later. With the &lt;strong&gt;&lt;em&gt;ForAll&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;Iff&lt;/em&gt;&lt;/strong&gt;
functions, clues 3, 5, 7, 13, and 14 can be encoded.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;h&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;House&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# A dummy constant.&lt;/span&gt;
&lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# 3. The Spaniard owns the dog.&lt;/span&gt;
    &lt;span class="n"&gt;ForAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Iff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nationality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;spain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;

    &lt;span class="c1"&gt;# 5. The Ukrainian drinks tea.&lt;/span&gt;
    &lt;span class="n"&gt;ForAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Iff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nationality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ukraine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drinks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;tea&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;

    &lt;span class="c1"&gt;# 7. The Old Gold smoker owns snails.&lt;/span&gt;
    &lt;span class="n"&gt;ForAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Iff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;smokes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;oldgold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;snails&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;

    &lt;span class="c1"&gt;# 13. The Lucky Strike smoker drinks orange juice.&lt;/span&gt;
    &lt;span class="n"&gt;ForAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Iff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;smokes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;luckystrikes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drinks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;juice&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;

    &lt;span class="c1"&gt;# 14. The Japanese smokes Parliaments.&lt;/span&gt;
    &lt;span class="n"&gt;ForAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Iff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nationality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;japan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;smokes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;parliaments&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that the same dummy constant &lt;strong&gt;&lt;em&gt;h&lt;/em&gt;&lt;/strong&gt; can be used in all of the
assertions.&lt;/p&gt;
&lt;p&gt;The function &lt;strong&gt;&lt;em&gt;house_number&lt;/em&gt;&lt;/strong&gt; has been declared from houses to
integers, but it is under specified. If the houses are numbered from
one to five, left to right, clues 1, 6, 9, and 10 can be formulated.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. There are five houses.&lt;/span&gt;
    &lt;span class="n"&gt;ForAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;And&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;house_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;house_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;

    &lt;span class="c1"&gt;# 6. The green house is immediately to the right of the ivory house.&lt;/span&gt;
    &lt;span class="n"&gt;house_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;green_house&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;house_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ivory_house&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;# 9. Milk is drunk in the middle house.&lt;/span&gt;
    &lt;span class="n"&gt;ForAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Iff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;house_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drinks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;milk&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;

    &lt;span class="c1"&gt;# 10. The Norwegian lives in the first house.&lt;/span&gt;
    &lt;span class="n"&gt;ForAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Iff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nationality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;norway&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;house_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The assertion for the first rule is really requiring the house numbers
to go from 1 to 5. The definition of the &lt;strong&gt;&lt;em&gt;House&lt;/em&gt;&lt;/strong&gt; sort already fixed
the cardinality at five.&lt;/p&gt;
&lt;h4&gt;Constraints involving two variables&lt;/h4&gt;
&lt;p&gt;Clue 11 states, "The man who smokes Chesterfields lives in the house
next to the man with the fox." This is a statement about two houses
neither specified in terms of the independent variable, color. This
can be handled by using a universal quantifier over two house
variables, &lt;strong&gt;&lt;em&gt;h&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;g&lt;/em&gt;&lt;/strong&gt;, with predicates &lt;code&gt;smokes(h) ==
chesterfields&lt;/code&gt; and &lt;code&gt;pet(g) == fox&lt;/code&gt;. When the predicates hold, the
houses must have consecutive house numbers.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Consts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;h g&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;next_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;house_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;house_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;house_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;house_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# 11. The man who smokes Chesterfields lives in the house next&lt;/span&gt;
    &lt;span class="c1"&gt;# to the man with the fox.&lt;/span&gt;
    &lt;span class="n"&gt;ForAll&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;Implies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;And&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;smokes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;chesterfields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;fox&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;next_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here &lt;strong&gt;&lt;em&gt;Implies&lt;/em&gt;&lt;/strong&gt; is used instead of &lt;strong&gt;&lt;em&gt;Iff&lt;/em&gt;&lt;/strong&gt; since the implication
is in one direction only. Two houses may be next to each other without
the two predicates holding.&lt;/p&gt;
&lt;p&gt;Clues 12 and 15 are handled similarly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# 12. Kools are smoked in the house next to the house where the horse is kept.&lt;/span&gt;
    &lt;span class="n"&gt;ForAll&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;Implies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;And&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;smokes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;kools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;horse&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;next_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)),&lt;/span&gt;

    &lt;span class="c1"&gt;# 15. The Norwegian lives next to the blue house.&lt;/span&gt;
    &lt;span class="n"&gt;ForAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Implies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;nationality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;norway&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;next_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blue_house&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Bijectivity constraints&lt;/h4&gt;
&lt;p&gt;Finally, constraints need to be added regarding the one-to-one
correspondence amongst all the dimensions. This requires all of the
functions &lt;strong&gt;&lt;em&gt;house_number&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;nationality&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;pet&lt;/em&gt;&lt;/strong&gt;,
&lt;strong&gt;&lt;em&gt;drinks&lt;/em&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;em&gt;smokes&lt;/em&gt;&lt;/strong&gt; to be injective. Given any two houses,
&lt;strong&gt;&lt;em&gt;h&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;g&lt;/em&gt;&lt;/strong&gt;, if they are distinct, their images under each of
the functions must be distinct. Because the house color has been used
as the independent variable, it doesn't appear in the
expression. Instead there is a requirement that the house numbers be
distinct.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# In the interest of clarity, it must be added that each of the five&lt;/span&gt;
    &lt;span class="c1"&gt;# houses is painted a different color, and their inhabitants are of&lt;/span&gt;
    &lt;span class="c1"&gt;# different national extractions, own different pets, drink different&lt;/span&gt;
    &lt;span class="c1"&gt;# beverages and smoke different brands of American cigarets [sic].&lt;/span&gt;
    &lt;span class="n"&gt;ForAll&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;Implies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;And&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;house_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;house_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;nationality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;nationality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;drinks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;drinks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;smokes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;smokes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Solution&lt;/h3&gt;
&lt;p&gt;With all of the constraints added to the solver, the satisfiability of
the system can be checked. If the system can be satisfied, the solver
can provide a &lt;em&gt;model&lt;/em&gt; which does so.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;sat&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Such a model provides explicit definitions of all the declared
functions and can evaluate arbitrary expressions. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;In [1]: model.eval(house_number(red_house))
Out[1]: 3

In [2]: model.eval(smokes(ivory_house))
Out[2]: luckystrikes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The puzzle asks, "who drinks water? Who owns the zebra?" These
questions can be answered by declaring two constants of the
&lt;strong&gt;&lt;em&gt;House&lt;/em&gt;&lt;/strong&gt; sort which are constrained to meet the two predicates.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Now, who drinks water? Who owns the zebra?&lt;/span&gt;

&lt;span class="n"&gt;the_water_house&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;the_water_house&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;House&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;the_zebra_house&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;the_zebra_house&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;House&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;drinks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;the_water_house&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;water&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;the_zebra_house&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;zebra&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;sat&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The resulting model can then return explicit values for the two
constants.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;In [3]: model.eval(the_water_house)
Out[3]: yellow

In [4]: model.eval(the_zebra_house)
Out[4]: green
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If "who" means the nationality of the inhabitants, that can be
answered, too.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;In [5]: model.eval(nationality(the_water_house))
Out[5]: norway

In [6]: model.eval(nationality(the_zebra_house))
Out[6]: japan
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The Norwegian drinks the water, and the person with the zebra is
Japanese.&lt;/p&gt;
&lt;h3&gt;Code&lt;/h3&gt;
&lt;p&gt;The full &lt;a href="https://github.com/benanhalt/logic-puzzles/blob/main/zebra.py"&gt;code for this
example&lt;/a&gt;
can be found in &lt;a href="https://github.com/benanhalt/logic-puzzles"&gt;my github
repository&lt;/a&gt; which also
contains other examples and approaches.&lt;/p&gt;</content><category term="programming"></category><category term="math"></category><category term="programming"></category><category term="logic"></category><category term="z3"></category></entry><entry><title>Rocks and Hailstones</title><link href="https://con.cor.dance/rocks-and-hailstones.html" rel="alternate"></link><published>2024-03-22T00:00:00-05:00</published><updated>2024-03-22T00:00:00-05:00</updated><author><name>Ben Anhalt</name></author><id>tag:con.cor.dance,2024-03-22:/rocks-and-hailstones.html</id><summary type="html">&lt;p&gt;I had fun solving the second part of &lt;a href="https://adventofcode.com/2023/day/24"&gt;Day 24 of Advent of Code this
year&lt;/a&gt;. This was a more
mathematical problem than most, and by working with vectors and
matrices it can be solved with minimal faffing about at the component
level.&lt;/p&gt;
&lt;p&gt;We are given a set of initial …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I had fun solving the second part of &lt;a href="https://adventofcode.com/2023/day/24"&gt;Day 24 of Advent of Code this
year&lt;/a&gt;. This was a more
mathematical problem than most, and by working with vectors and
matrices it can be solved with minimal faffing about at the component
level.&lt;/p&gt;
&lt;p&gt;We are given a set of initial positions and velocities for a number, &lt;span class="math"&gt;\(n&amp;gt;
3\)&lt;/span&gt;, of hailstones. The hailstones travel in straight lines at constant
velocities, and we are asked to find the initial position and velocity
of rock thrown so that it will impact (without then changing its
velocity) all of the given hailstones. We are told the the paths of
the hailstones are such that this is possible.&lt;/p&gt;
&lt;p&gt;Let &lt;span class="math"&gt;\(\vec{r}_i\)&lt;/span&gt; and &lt;span class="math"&gt;\(\vec{v}_i\)&lt;/span&gt; represent the initial position and
velocity vectors of the &lt;span class="math"&gt;\(i\)&lt;/span&gt;th hailstone. These are given. Let
&lt;span class="math"&gt;\(\vec{s}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\vec{u}\)&lt;/span&gt; represent the initial position and
velocity vectors of the thrown rock. These are the unknowns we are
solving for. If the thrown rock impacts the &lt;span class="math"&gt;\(i\)&lt;/span&gt;th hailstone at time
&lt;span class="math"&gt;\(t_i\)&lt;/span&gt;, their positions at that time must be the same.&lt;/p&gt;
&lt;div class="math"&gt;$$ \vec{u} t_i + \vec{s} = \vec{v}_i t_i + \vec{r}_i $$&lt;/div&gt;
&lt;div class="math"&gt;$$ (\vec{u} - \vec{v}_i)t_i = \vec{r}_i - \vec{s} $$&lt;/div&gt;
&lt;p&gt;Remembering that the cross product of parallel vectors is zero, we can
eliminate &lt;span class="math"&gt;\(t_i\)&lt;/span&gt;, which is an unknown we don't care about, by taking the
cross product of both sides with &lt;span class="math"&gt;\((\vec{u} - \vec{v}_i)\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="math"&gt;$$ (\vec{u} - \vec{v}_i)\times(\vec{u} - \vec{v}_i)t_i = (\vec{u} - \vec{v}_i)\times(\vec{r}_i - \vec{s}) $$&lt;/div&gt;
&lt;div class="math"&gt;$$ \vec{0} = (\vec{u} - \vec{v}_i)\times(\vec{r}_i - \vec{s}) $$&lt;/div&gt;
&lt;p&gt;Geometrically, this is saying the velocity of the thrown rock must be
parallel to the vector between its initial position and that of the
hailstone. This makes sense. We proceed by expanding out the
multiplication.&lt;/p&gt;
&lt;div class="math"&gt;$$ \vec{0} = \vec{u} \times \vec{r}_i - \vec{u} \times \vec{s} -
\vec{v}_i \times \vec{r}_i + \vec{v}_i \times \vec{s} $$&lt;/div&gt;
&lt;p&gt;The terms &lt;span class="math"&gt;\(\vec{u} \times \vec{r}_i\)&lt;/span&gt; and &lt;span class="math"&gt;\(\vec{v}_i \times \vec{s}\)&lt;/span&gt;
are linear in the unknowns and unproblematic. The term &lt;span class="math"&gt;\(\vec{v}_i
\times \vec{r}_i\)&lt;/span&gt; is a constant formed from the givens. In contrast,
&lt;span class="math"&gt;\(\vec{u} \times \vec{s}\)&lt;/span&gt; represents products among the unknowns and
would prevent solution by standard methods for systems of linear
equations. On the other hand, this term &lt;em&gt;only&lt;/em&gt; includes properties of
the thrown rock. This means it is independent of the impacted
hailstone &lt;span class="math"&gt;\(i\)&lt;/span&gt; since all hailstones are struck by the same rock. The
choice of the &lt;span class="math"&gt;\(i\)&lt;/span&gt;th hailstone in obtaining the above equation was
arbitrary. We can chose a different hailstone &lt;span class="math"&gt;\(j \ne i\)&lt;/span&gt; and get the
same equation with the &lt;span class="math"&gt;\(i\)&lt;/span&gt; subscripts replaced with &lt;span class="math"&gt;\(j\)&lt;/span&gt;. Subtracting
the two equations will then eliminate the quadratic &lt;span class="math"&gt;\(\vec{u} \times
\vec{s}\)&lt;/span&gt; term.&lt;/p&gt;
&lt;div class="math"&gt;$$
\begin{align}
\vec{u} \times \vec{r}_i - \vec{u} \times \vec{s} -
\vec{v}_i \times \vec{r}_i + \vec{v}_i \times \vec{s} &amp;amp;=&amp;amp;\vec{0} \\
\vec{u} \times \vec{r}_j - \vec{u} \times \vec{s} -
\vec{v}_j \times \vec{r}_j + \vec{v}_j \times \vec{s}  &amp;amp;=&amp;amp;\vec{0} \\
\hline \\
\vec{u} \times (\vec{r}_i - \vec{r}_j)
- \vec{v}_i \times \vec{r}_i + \vec{v}_j \times \vec{r}_j
+ (\vec{v}_i  - \vec{v}_j) \times \vec{s} &amp;amp;=&amp;amp;\vec{0}
\end{align}
$$&lt;/div&gt;
&lt;p&gt;We rearrange the resulting equation by moving the constant terms to
the right hand side and reversing the cross product involving
&lt;span class="math"&gt;\(\vec{u}\)&lt;/span&gt;. Reversing the cross product introduces a minus sign which
we absorb by reversing the subtraction in &lt;span class="math"&gt;\((\vec{r}_i - \vec{r}_j)\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="math"&gt;$$
(\vec{v}_i  - \vec{v}_j) \times \vec{s} + (\vec{r}_j - \vec{r}_i)
\times \vec{u} 
= \vec{v}_i \times \vec{r}_i - \vec{v}_j \times \vec{r}_j
$$&lt;/div&gt;
&lt;p&gt;This is a system of three linear equations with six unknowns. Another
three equations are required to fully determine the solution. These
can be obtained by replacing the arbitrary choice of the &lt;span class="math"&gt;\(j\)&lt;/span&gt;th
hailstone with a third hailstone &lt;span class="math"&gt;\(k\)&lt;/span&gt; distinct from both &lt;span class="math"&gt;\(j\)&lt;/span&gt; and &lt;span class="math"&gt;\(i\)&lt;/span&gt; to
yield a new, independent vector equation.&lt;/p&gt;
&lt;div class="math"&gt;$$
(\vec{v}_i  - \vec{v}_k) \times \vec{s} + (\vec{r}_k - \vec{r}_i)
\times \vec{u} 
= \vec{v}_i \times \vec{r}_i - \vec{v}_k \times \vec{r}_k
$$&lt;/div&gt;
&lt;p&gt;To solve this linear system we need to translate the two vector
equations into one six dimensional matrix equation of the form
&lt;span class="math"&gt;\(\boldsymbol{Mx}=\boldsymbol{b}\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The cross products can be represented in &lt;a href="https://en.wikipedia.org/wiki/Cross_product#Conversion_to_matrix_multiplication"&gt;matrix
form&lt;/a&gt;,
&lt;span class="math"&gt;\(\vec{a}\times\vec{b} = [\vec{a}]_\times\boldsymbol{b}\)&lt;/span&gt;
where &lt;span class="math"&gt;\(\boldsymbol{b}\)&lt;/span&gt; is &lt;span class="math"&gt;\(\vec{b}\)&lt;/span&gt; as a column vector and the matrix
&lt;span class="math"&gt;\([\vec{a}]_\times\)&lt;/span&gt; is defined as&lt;/p&gt;
&lt;div class="math"&gt;$$
[\vec{a}]_{\times} \stackrel{\rm def}{=} \begin{bmatrix}\,\,0&amp;amp;\!-a_3&amp;amp;\,\,\,a_2\\\,\,\,a_3&amp;amp;0&amp;amp;\!-a_1\\\!-a_2&amp;amp;\,\,a_1&amp;amp;\,\,0\end{bmatrix}
$$&lt;/div&gt;
&lt;p&gt;with &lt;span class="math"&gt;\([a_1\, a_2\, a_3]^T = \boldsymbol{a}\)&lt;/span&gt; being the components of
&lt;span class="math"&gt;\(\vec{a}\)&lt;/span&gt;. &lt;/p&gt;
&lt;p&gt;With this replacement the equations will take the forms&lt;/p&gt;
&lt;div class="math"&gt;$$ [\vec{v}_i - \vec{v}_j]_\times \boldsymbol{s} + [\vec{r}_j -
\vec{r}_i]_\times \boldsymbol{u} = [\vec{v}_i]_\times \boldsymbol{r}_i -
[\vec{v}_j]_\times \boldsymbol{r}_j
$$&lt;/div&gt;
&lt;p&gt;
and
&lt;/p&gt;
&lt;div class="math"&gt;$$ [\vec{v}_i - \vec{v}_k]_\times \boldsymbol{s} + [\vec{r}_k -
\vec{r}_i]_\times \boldsymbol{u} = [\vec{v}_i]_\times \boldsymbol{r}_i -
[\vec{v}_k]_\times \boldsymbol{r}_k.
$$&lt;/div&gt;
&lt;p&gt;We'll also need to represent all six unknowns in one column vector
&lt;span class="math"&gt;\(\boldsymbol{x}\)&lt;/span&gt; which we can define by stacking &lt;span class="math"&gt;\(\boldsymbol{s}\)&lt;/span&gt; atop
&lt;span class="math"&gt;\(\boldsymbol{u}\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="math"&gt;$$\boldsymbol{x} \stackrel{\rm def}{=} \begin{bmatrix}s_1\\ s_2\\ s_3\\ u_1\\ u_2\\ u_3\end{bmatrix}$$&lt;/div&gt;
&lt;p&gt;Now the system of equations can be written using block matrices as&lt;/p&gt;
&lt;div class="math"&gt;$$
\left[\begin{array}{@{}c|c@{}}
  [\vec{v}_i - \vec{v}_j]_\times &amp;amp;  [\vec{r}_j - \vec{r}_i]_\times \\\hline
  [\vec{v}_i - \vec{v}_k]_\times &amp;amp;  [\vec{r}_k - \vec{r}_i]_\times \\
\end{array}\right]\boldsymbol{x} = \begin{bmatrix}
[\vec{v}_i]_\times \boldsymbol{r}_i - [\vec{v}_j]_\times \boldsymbol{r}_j \\\hline
[\vec{v}_i]_\times \boldsymbol{r}_i - [\vec{v}_k]_\times \boldsymbol{r}_k
\end{bmatrix}.
$$&lt;/div&gt;
&lt;p&gt;This is a six dimensional system of equations where the left hand side
consists of a &lt;span class="math"&gt;\(6\times6\)&lt;/span&gt; square matrix &lt;span class="math"&gt;\(\boldsymbol{M}\)&lt;/span&gt; times our six
component column vector of unknowns, and the right hand side is a
&lt;span class="math"&gt;\(6\times1\)&lt;/span&gt; column vector &lt;span class="math"&gt;\(\boldsymbol{b}\)&lt;/span&gt;. This system can be solved
using standard methods such as Gaussian elimination. To compute
&lt;span class="math"&gt;\(\boldsymbol{M}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\boldsymbol{b}\)&lt;/span&gt;, we just need functions for the
cross product matrix &lt;span class="math"&gt;\([\vec{v}]_\times\)&lt;/span&gt;, for the multiplication of a
vector by a matrix, for subtracting two vectors, and for joining
matrices horizontally and vertically into block matrices.&lt;/p&gt;
&lt;p&gt;I had hoped to use the Haskell &lt;code&gt;hmatrix&lt;/code&gt; package since it includes the
necessary functions except for &lt;span class="math"&gt;\([\vec{a}]_\times\)&lt;/span&gt; and has a &lt;code&gt;solve&lt;/code&gt;
function for linear systems. Unfortunately it doesn't support
arbitrary precision rational numbers which is required to produce the
exact answer called for by AoC. I ended up writing my own
implementation of the functions and adapted a Guassian elimination
routine from
&lt;a href="https://haskellicious.wordpress.com/2012/11/26/the-gauss-algorithm-in-haskell/"&gt;https://haskellicious.wordpress.com/2012/11/26/the-gauss-algorithm-in-haskell/&lt;/a&gt;. I
won't include or discuss the code here since I just wanted to
highlight the mathematical aspects. The interested reader can find the
code in my &lt;a href="https://github.com/benanhalt/AoC2023/blob/main/day24/solution.hs"&gt;Advent of Code
solutions&lt;/a&gt;
repository.&lt;/p&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="math"></category><category term="math"></category><category term="adventofcode"></category></entry><entry><title>Migrating the Specify 6 SVN Repository to Git</title><link href="https://con.cor.dance/migrating-the-specify-6-svn-repository-to-git.html" rel="alternate"></link><published>2017-10-30T12:49:25-05:00</published><updated>2017-10-30T12:49:25-05:00</updated><author><name>Ben Anhalt</name></author><id>tag:con.cor.dance,2017-10-30:/migrating-the-specify-6-svn-repository-to-git.html</id><summary type="html">&lt;p&gt;Until now the Specify 6 source code has been version controlled using
Subversion. The decision was made to migrate the repository to
Git. Here are some notes about how this was accomplished.&lt;/p&gt;
&lt;h2&gt;First Step: git-svn&lt;/h2&gt;
&lt;p&gt;The rough draft of the migration was obtained using the &lt;em&gt;git-svn&lt;/em&gt;
tool. One can find …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Until now the Specify 6 source code has been version controlled using
Subversion. The decision was made to migrate the repository to
Git. Here are some notes about how this was accomplished.&lt;/p&gt;
&lt;h2&gt;First Step: git-svn&lt;/h2&gt;
&lt;p&gt;The rough draft of the migration was obtained using the &lt;em&gt;git-svn&lt;/em&gt;
tool. One can find plenty of material about its use, so I will mostly
just describe the particular choices I made as a sort of case study.&lt;/p&gt;
&lt;h3&gt;Making the authors.txt file&lt;/h3&gt;
&lt;p&gt;Because SVN commits are labeled by SVN user names while Git uses email
addresses, &lt;em&gt;git-svn&lt;/em&gt; accepts a mapping file that it uses to determine
the author of the commits as they are replayed into the Git
repository. The SVN user names can be easily obtained from the log.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;svn&lt;span class="w"&gt; &lt;/span&gt;log&lt;span class="w"&gt; &lt;/span&gt;-q&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^r&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;BEGIN { FS = &amp;quot;|&amp;quot; }; { print $2 }&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sort&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;authors.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In the resulting &lt;code&gt;authors.txt&lt;/code&gt; file each user name will need to have
its corresponding email addresses added. In the case of Specify there
were several authors listed multiple times under different user
names. This means that same email was sometimes added on multiple
lines. The mapping need not be injective.&lt;/p&gt;
&lt;p&gt;The effort to define the mapping is worthwhile, particularly when the
resulting repository will be pushed GitHub. Having the real email
addresses means contributors will be correctly attributed.&lt;/p&gt;
&lt;p&gt;An interesting aspect of this process is determining the identities of
past contributors given only the SVN user. My approach was searching
through the project mailing list archive. Everyone who has worked on the
project used it at some point, and it records their real names and
email addresses.&lt;/p&gt;
&lt;h3&gt;Branches and Tags&lt;/h3&gt;
&lt;p&gt;It is possible to point &lt;em&gt;git-svn&lt;/em&gt; to the tags and branches directories
of an SVN repository and have it try to recapitulate the branch and
merge history in Git. I made an initial attempt at using this
capability, but it didn't seem to work on the first try. I decided
that since we haven't been using SVN branches for the past few years,
I wouldn't bother with trying to preserve them. From a preservation
perspective it makes more sense to archive the SVN repository
as a primary source rather than worrying about producing exact
fidelity in a derivative artifact that's primary use is day-to-day
development work.&lt;/p&gt;
&lt;p&gt;With the completed &lt;code&gt;authors.txt&lt;/code&gt; file in hand, a simple migration of
the SVN trunk into Git is straightforward.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git-svn&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;--prefix&lt;span class="o"&gt;=&lt;/span&gt;svn/&lt;span class="w"&gt; &lt;/span&gt;--trunk&lt;span class="o"&gt;=&lt;/span&gt;trunk/Specify&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;--authors-file&lt;span class="o"&gt;=&lt;/span&gt;authors.txt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;https://svn.code.sf.net/p/specify/code&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;specify2git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I don't remember exactly how long it took for &lt;em&gt;git-svn&lt;/em&gt; to replay the
roughly 12,000 commits into the new Git repository. Perhaps an
hour. Too long to sit and watch, but not agonizingly slow, either.&lt;/p&gt;
&lt;h2&gt;Rewriting History&lt;/h2&gt;
&lt;p&gt;The resulting Git repository was approximately 600MB. A lot of the
bulk comes from the project dependencies that are included in the
repository as JAR files. This is not an ideal situation, but it is
what it is. I briefly investigated some sort of &lt;em&gt;git-annex&lt;/em&gt; solution
to separate the libraries, but decided to take things one step at a
time.&lt;/p&gt;
&lt;p&gt;Still, I wanted to see what were the worst offenders in terms of file
size. Perhaps I would learn something that would produce an easy win
for reducing the bloat. Some Googling led me to
a &lt;a href="https://stackoverflow.com/a/42544963"&gt;StackOverflow answer&lt;/a&gt;
for finding the largest objects in a Git repository.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;rev-list&lt;span class="w"&gt; &lt;/span&gt;--objects&lt;span class="w"&gt; &lt;/span&gt;--all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;cat-file&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;--batch-check&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%(objecttype) %(objectname) %(objectsize) %(rest)&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^blob&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sort&lt;span class="w"&gt; &lt;/span&gt;--numeric-sort&lt;span class="w"&gt; &lt;/span&gt;--key&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Sure enough, the largest object was an SQL file containing test data that
had been removed at some point in the past. It was over 170MB. I was
pretty sure it could be safely removed from the project history, but I
wanted to see why it was there and when it had gone away.&lt;/p&gt;
&lt;p&gt;You can see the history of deleted files using &lt;em&gt;git log&lt;/em&gt;, but you have
to say &lt;code&gt;git log -- path/to/deleted/file&lt;/code&gt; or else Git will think the file
path is the name of a branch or something.&lt;/p&gt;
&lt;p&gt;In this case there were only two commits that touched the file. The
one that added it and the immediate successor with the message
"Accidently checked in." No doubt, then. I could remove this file from the
history and immediately reduce the size of the repository by nearly
30%.&lt;/p&gt;
&lt;p&gt;I've previously used &lt;em&gt;git rebase&lt;/em&gt; to rewrite history, but I was aware there
were better options for these kind of bulk operations. This time
Google turned up an excellent post from Manish
Goregaokar,
&lt;a href="https://manishearth.github.io/blog/2017/03/05/understanding-git-filter-branch/"&gt;Understanding Git filter-branch and the Git Storage Model&lt;/a&gt;. I
was quickly able to figure out how to excise the file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;filter-branch&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;--prune-empty&lt;span class="w"&gt; &lt;/span&gt;--index-filter&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;git rm --cached --ignore-unmatch path/to/file&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;HEAD
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The operation took about five minutes to complete. &lt;/p&gt;
&lt;p&gt;Emboldened by this success, I returned to the list of large files in
search of more bloat. I was able to eliminate a few large demo
files. I also found instances where whole subdirectories had been
accidentally committed. In one, someone had committed the actual
installation directory into the repository, including an embedded MySQL
database! In another, the entire source code repository had been added
as a subdirectory of itself. The latter didn't add much to the size since
Git only stores a single copy of identical files. Nevertheless, I
removed it just the same.&lt;/p&gt;
&lt;p&gt;Besides new files, some of these accidental commits also included
other changes that were reverted by a subsequent commit. I removed
these pairs of commits using &lt;code&gt;git rebase -i&lt;/code&gt; on branches I created
pointing to the reverting commit. I then used &lt;code&gt;git rebase --onto&lt;/code&gt; to
rewrite the later history onto those branches.&lt;/p&gt;
&lt;p&gt;If I ever do something like this again, I should remember to search
the commit logs for "accident*" and similar messages.&lt;/p&gt;
&lt;h2&gt;The Result&lt;/h2&gt;
&lt;p&gt;After all of this, the Git repository ended up being about 350MB
according to &lt;code&gt;du -h .git/objects/&lt;/code&gt;. Because Git retains old references
when using &lt;em&gt;filter-branch&lt;/em&gt; and &lt;em&gt;rebase&lt;/em&gt;, the size reduction is not
immediately apparent even after running &lt;em&gt;git gc&lt;/em&gt;. The easiest way to
get an accurate measurement is to make a new clone of the repository
using &lt;code&gt;git clone file://path/to/cleaned/up/repository new-clone&lt;/code&gt;. Only
the objects actually reachable from &lt;em&gt;HEAD&lt;/em&gt; will be brought over, but
it is necessary to use the &lt;code&gt;file://&lt;/code&gt; style URI. Cloning the directory
directly will only create hard links.&lt;/p&gt;</content><category term="programming"></category><category term="svn"></category><category term="git"></category><category term="github"></category></entry><entry><title>Using Leaflet maps in an Elm app</title><link href="https://con.cor.dance/using-leaflet-maps-in-an-elm-app.html" rel="alternate"></link><published>2017-08-30T00:00:00-05:00</published><updated>2017-08-30T00:00:00-05:00</updated><author><name>Ben Anhalt</name></author><id>tag:con.cor.dance,2017-08-30:/using-leaflet-maps-in-an-elm-app.html</id><summary type="html">&lt;p&gt;I recently started a project creating a web app that requires
displaying maps. This was to be a browser based app, but I wanted to
use something safer and more maintainable than Javascript. I decided
to give &lt;a href="http://elm-lang.org/"&gt;Elm&lt;/a&gt; a try and have been fairly happy
with it. However, I couldn't …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently started a project creating a web app that requires
displaying maps. This was to be a browser based app, but I wanted to
use something safer and more maintainable than Javascript. I decided
to give &lt;a href="http://elm-lang.org/"&gt;Elm&lt;/a&gt; a try and have been fairly happy
with it. However, I couldn't find any pure Elm libraries for
displaying maps that supported WMS layers and other features that
would be needed.&lt;/p&gt;
&lt;p&gt;So, the question became whether it would be possible to use a
Javascript mapping library with my Elm application. It turns out that
it is not too difficult to get basic rendering of maps to work using
the &lt;a href="http://leafletjs.com/"&gt;Leaflet&lt;/a&gt; library. That will be the subject
of this post.&lt;/p&gt;
&lt;p&gt;In the future, I will attempt to add more advanced features such as
selecting regions within a displayed map.&lt;/p&gt;
&lt;h2&gt;Paradigm conflict&lt;/h2&gt;
&lt;p&gt;The main difficulty with using Elm and Leaflet together is that Elm is
totally based on the reactive paradigm where the entire state of your
app is captured in a single immutable data structure from which the
DOM is derived via a pure view function. Any change in what the user
sees has to be accomplished by updating the state (&lt;em&gt;Model&lt;/em&gt; in Elm
parlance) in some way and allowing the DOM to be recomputed. This way
of doing things and the resulting program structure are
called
&lt;a href="https://guide.elm-lang.org/architecture/"&gt;The Elm Architecture&lt;/a&gt;, or
&lt;em&gt;TEA&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Behind the scenes the view function actually generates &lt;em&gt;virtual&lt;/em&gt; DOM
structures which are diffed to compute mutations to the real DOM. This
is so that minor changes don't cause the whole page to be
redrawn. However, this mechanism is not visible to the application
programmer.&lt;/p&gt;
&lt;p&gt;On the other hand, Leaflet and similar Javascript map libraries make
use of mutation and side effects with full abandon. The DOM is treated
as a persistent mutable object within which div elements may have a map
objects attached. The map object then provides methods such as
&lt;code&gt;setView&lt;/code&gt; or &lt;code&gt;addLayer&lt;/code&gt; that mutate the state of the map and update
the DOM to effect the change.&lt;/p&gt;
&lt;h2&gt;Elm interop with Javascript&lt;/h2&gt;
&lt;p&gt;The primary means of interoperating with external Javascript in Elm is
through &lt;em&gt;ports&lt;/em&gt;. These allow incoming messages to be subscribed to,
and outgoing messages to be dispatched. Incoming messages result in
calls to a model update function when received. Outgoing messages
can be generated upon model updates.&lt;/p&gt;
&lt;p&gt;My initial approach was to use this port system to send messages out
to Javascript for actions like: add a map to the div with id
&lt;code&gt;foobar&lt;/code&gt;, remove the map from the div, add this WMS layer. I
managed to get this scheme to work, but it was complicated and seemed
contrary to &lt;em&gt;TEA&lt;/em&gt;. I was essentially maintaining
program state in two separate locations, the Elm model and the Leaflet
interface, and shuttling messages back and forth
to keep them in sync.&lt;/p&gt;
&lt;p&gt;What I really wanted was something like the way regular HTML is
rendered in Elm, where the map could just be computed purely from
the model.&lt;/p&gt;
&lt;h2&gt;Enter mutation observers&lt;/h2&gt;
&lt;p&gt;Ideally, it would be possible to tie-in to or extend the Elm virtual DOM
system. There is an undocumented Elm feature called &lt;em&gt;Native Modules&lt;/em&gt; that
might make that possible, but I haven't investigated that very much.&lt;/p&gt;
&lt;p&gt;Instead, I have currently settled on a solution that involves adding a HTML5
data attribute with the map state to the to-be Leaflet container divs. These
divs are also given the class &lt;code&gt;leaflet-map&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;view&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;WMSInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Html&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;msg&lt;/span&gt;
&lt;span class="nv"&gt;view&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;wmsInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;div&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;leaflet-map&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;attribute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;data-leaflet&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;serialize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;wmsInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Currently, the only map state I care about is a list of WMS layers to add
to the map. The &lt;code&gt;serialize&lt;/code&gt; function just makes JSON out of the layer information.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Json.Encode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;exposing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;(..)&lt;/span&gt;


&lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;WMSInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mapName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;layers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;endPoint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nv"&gt;serialize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;WMSInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;
&lt;span class="nv"&gt;serialize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;map&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;object&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;mapName&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mapName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;endPoint&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;endPoint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;layers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;layers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;|&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;|&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;list&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;encode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, on the Javascript side I can watch for mutations to the DOM
involving elements with the &lt;code&gt;leaflet-map&lt;/code&gt; class and invoke the necessary
Leaflet methods to match the state in the data attribute. The relevant DOM 
mutations are when elements with the &lt;code&gt;leaf-map&lt;/code&gt; class
are added or removed anywhere in the page body and when the &lt;code&gt;data-leaflet&lt;/code&gt;
attribute changes on any element.&lt;/p&gt;
&lt;p&gt;There is a Web API for doing such things called
&lt;em&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver"&gt;MutationObserver&lt;/a&gt;&lt;/em&gt;.
The way it is used is to create an observer instance with a callback function
that receives mutation events, and then activate the observer with some filters about
what kinds of mutations to look for.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MutationObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;processMutations&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;subtree&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;childList&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;attributeFilter&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data-leaflet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;subtree&lt;/code&gt; flag means to watch for mutations anywhere in the descendants of
the observed element, which is the entire page body in this case. The &lt;code&gt;childList&lt;/code&gt;
flag will catch additions or removals of elements. The &lt;code&gt;attributes&lt;/code&gt; flag indicates
that changes to element attributes are of interest, but the &lt;code&gt;attributeFilter&lt;/code&gt; allows
the parameter allows that observation to be limited to only the &lt;code&gt;data-leaflet&lt;/code&gt;
attribute that contains the map information.&lt;/p&gt;
&lt;h2&gt;Mutating the map&lt;/h2&gt;
&lt;p&gt;Using this mechanism to observe relevant mutations to the DOM, the corresponding
calls to the Leaflet library can be made to make it match the desired state.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;processMutations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;processAddedNodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addedNodes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;processRemovedNodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removedNodes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;attributes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;updateMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The mutation observer invokes its callback with a list of
mutations. Each mutation object has a list added and a list of removed
child nodes, either of which may be empty if there was nothing added
or removed.  There is also a type attribute that indicates the type of
mutation. Since there is only one attribute that matches the attribute
filter, any mutation of the type &lt;code&gt;"attributes"&lt;/code&gt; means the element that
was mutated, which is &lt;code&gt;m.target&lt;/code&gt;, is a map container div that needs
its map to be updated.&lt;/p&gt;
&lt;p&gt;Taking added nodes first, each added node is examined for any elements
with the class &lt;code&gt;leaflet-map&lt;/code&gt;, because the added node may contain an
entire subtree with multiple maps. For each matching element, the
Leaflet map constructor is invoked. Leaflet set an undocumented
attribute, &lt;code&gt;_leaflet_id&lt;/code&gt;, on the container element. This unique id is
used as the key in a global dictionary, called &lt;code&gt;maps&lt;/code&gt;, where the
Leaflet map object will be stored for later access. Finally,
&lt;code&gt;updateMap&lt;/code&gt; is called that to handle setting the map state, which will
be explained subsequently.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;processAddedNodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addedNodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;addedNodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementsByClassName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementsByClassName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;leaflet-map&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;crs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EPSG4326&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;setView&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;maps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_leaflet_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;added leaflet id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_leaflet_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;updateMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If a map is added or the &lt;code&gt;data-leaflet&lt;/code&gt; attribute is changed, the map
needs to be updated with current state. The Leaflet map object
instance is obtained from the global &lt;code&gt;maps&lt;/code&gt; dictionary according to
the &lt;code&gt;_leaflet_id&lt;/code&gt; attribute mentioned above. A separate global
&lt;code&gt;mapLayers&lt;/code&gt; dictionary keeps track of the layers that have been added
to the map. To accomplish the update, all existing layers are first
removed. Then, the JSON data that was stored in &lt;code&gt;data-leaflet&lt;/code&gt;
attribute needs to obtained. It will be contained in
the
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset"&gt;HTMLElement dataset property&lt;/a&gt;,
&lt;code&gt;element.dataset.leaflet&lt;/code&gt;. This is the JSON data serialized from the
Elm application. It is parsed and used to add layers to map. Each
created layer object is added to the &lt;code&gt;mapLayers&lt;/code&gt; global so they can be
accessed later.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;updateMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;maps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_leaflet_id&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;updating leaflet id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_leaflet_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;layers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mapLayers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_leaflet_id&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;layers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeLayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wmsInfos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;leaflet&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;mapLayers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_leaflet_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wmsInfos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wmsInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tileLayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wmsInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endPoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;mapName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wmsInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mapName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;image/png&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1.1.0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;transparent&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;layers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wmsInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;addTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When nodes are removed, the node's descendants are again searched
for the &lt;code&gt;leaflet-map&lt;/code&gt; class. Each such element is inspected for the &lt;code&gt;_leaflet_id&lt;/code&gt;
attribute described above. If the &lt;code&gt;_leaflet_id&lt;/code&gt; attribute is present, the &lt;code&gt;processAddedNodes&lt;/code&gt;
function must have added the map. This means the &lt;code&gt;_leaflet_id&lt;/code&gt; value will
be in the global &lt;code&gt;maps&lt;/code&gt; dictionary and can be used to obtain the corresponding Leaflet map
instance, allowing its &lt;code&gt;remove()&lt;/code&gt; method to be invoked. The corresponding &lt;code&gt;maps&lt;/code&gt; and
&lt;code&gt;mapLayers&lt;/code&gt; entries are then nulled out.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;processRemovedNodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;removedNodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;removedNodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementsByClassName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementsByClassName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;leaflet-map&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_leaflet_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;removing map with leaflet id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_leaflet_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;maps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_leaflet_id&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;maps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_leaflet_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;mapLayers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_leaflet_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;The method described in this article seems to work fairly well, and it
is what I am currently using in my project. I can't say it is entirely
satisfactory, because there is quite a bit of incidental complexity,
all of which is on the unsafe Javascript side of the interface.&lt;/p&gt;
&lt;p&gt;I will undoubtedly be revisiting this code. There other features that
are needed including selecting regions and synchronizing the view
between multiple maps.&lt;/p&gt;</content><category term="programming"></category><category term="elm"></category><category term="javascript"></category><category term="leaflet"></category></entry></feed>