<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <link type="text/html" rel="alternate" href="https://thepid.de"/>
  <link type="application/atom+xml" rel="self" href="https://thepid.de/feed"/>
  <author>
    <name>THE</name>
    <email>thepid@mailbox.org</email>
  </author>
  <id>https://thepid.de</id>
  <title>ThePid Feed</title>
  <updated>2026-09-19T09:46:45.387721Z</updated>
  <entry>
    <published>2023-02-15T21:00:52+01:00</published>
    <content type="html">&lt;p&gt;Hi there. My name is &lt;strong&gt;Tobi&lt;/strong&gt; and this is my freshly created homepage.
Here I want to write some interesting things which I come across in my life.
Also I want to improve my coding skills and play with Phoenix and Elixir.&lt;/p&gt;</content>
    <link type="image" rel="enclosure" href=""/>
    <link type="text/html" rel="alternate" href="https://thepid.de/blog/1"/>
    <id>1</id>
    <title>Servus</title>
    <updated>2023-02-15T21:00:52+01:00</updated>
  </entry>
  <entry>
    <published>2023-02-15T21:07:15+01:00</published>
    <content type="html">&lt;p&gt;I want to recommend the shop of my friend Jones, who has recently become a craftsman.
He has acquired a decent CNC for woodworking and creates now some beautiful earrings.&lt;/p&gt;
&lt;p&gt;I firsthand know his customers are very pleased and can affirm the superb quality of the artwork with my own hands.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;a href=&quot;https://www.etsy.com/de-en/shop/ZweigundZunder&quot;&gt;Zweig und Zunder&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://i.etsystatic.com/40107840/r/il/d6484f/4602760078/il_680x540.4602760078_ihq5.jpg&quot; alt=&quot;Earrings&quot; /&gt;&lt;/p&gt;</content>
    <link type="image" rel="enclosure" href="https://i.etsystatic.com/40107840/r/il/d6484f/4602760078/il_680x540.4602760078_ihq5.jpg"/>
    <link type="text/html" rel="alternate" href="https://thepid.de/blog/2"/>
    <id>2</id>
    <title>Zweig und Zunder</title>
    <updated>2024-08-25T20:40:13+02:00</updated>
  </entry>
  <entry>
    <published>2023-08-07T21:23:07+02:00</published>
    <content type="html">&lt;p&gt;While developing a certain part of thepid.de I needed to input datetime into a form.
Luckily html got you covered with &lt;code&gt;datetime-local&lt;/code&gt; as type attribute for an input field, which works well.
However I was pretty annoyed that the format of the datetime input in my browser was the American AM/PM format.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://minio.mutualisten.de/thepid/dtl_ampm.png&quot; alt=&quot;american datetime-local&quot; /&gt;&lt;/p&gt;
&lt;p&gt;My computer language is set to English, but locale for Date, Currency, etc.. is set to German.
This is done by setting the corresponding&lt;code&gt;LC_&lt;/code&gt; variables. Turns out firefox does not care about that.&lt;/p&gt;
&lt;p&gt;After some kagiing I found a solution: Go to about:config and switch &lt;code&gt;intl.regional_prefs.use_os_locales&lt;/code&gt; to true.&lt;/p&gt;
&lt;p&gt;Thanks &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1464592&quot;&gt;Zibi Braniecki&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now it looks so good.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://minio.mutualisten.de/thepid/dtl_german.png&quot; alt=&quot;german datetime-local&quot; /&gt;&lt;/p&gt;</content>
    <link type="image" rel="enclosure" href=""/>
    <link type="text/html" rel="alternate" href="https://thepid.de/blog/3"/>
    <id>3</id>
    <title>German datetimes please!</title>
    <updated>2023-08-07T21:34:11+02:00</updated>
  </entry>
  <entry>
    <published>2023-08-18T13:37:15+02:00</published>
    <content type="html">&lt;p&gt;I like being able to subscribe to random websites using rss/atom feeds. I self host a miniflux instance, where I manage my subscriptions and read articles. So naturally I wanted to implement such a feed also for thepid.de. I mean how hard can that be?&lt;/p&gt;
&lt;p&gt;Basically it involves two steps&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Implementing a function, which builds the feed&lt;/li&gt;
&lt;li&gt;Serving the feed as raw xml&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first step was straightforward as soon as I stumbled upon the &lt;code&gt;Atomex&lt;/code&gt; library. I just had to copy the example from the docs and make some adoptions regarding my schema.&lt;/p&gt;
&lt;pre lang=&quot;Elixir&quot;&gt;&lt;code&gt;  alias Atomex.Entry
  alias Atomex.Feed

  def build_feed(posts) do
    &quot;https://thepid.de&quot;
    |&gt; Feed.new(DateTime.utc_now(), &quot;ThePid Feed&quot;)
    |&gt; Feed.author(&quot;THE&quot;, email: &quot;thepid@mailbox.org&quot;)
    |&gt; Feed.link(&quot;https://thepid.de/feed&quot;, rel: &quot;self&quot;)
    |&gt; Feed.entries(Enum.map(posts, &amp;get_entry/1))
    |&gt; Feed.build()
    |&gt; Atomex.generate_document()
  end

  defp get_entry(post) do
    inserted_at = DateTime.from_naive!(post.inserted_at, &quot;Europe/Berlin&quot;, Tz.TimeZoneDatabase)
    updated_at = DateTime.from_naive!(post.updated_at, &quot;Europe/Berlin&quot;, Tz.TimeZoneDatabase)
    content = Earmark.as_html!(post.content, %Earmark.Options{code_class_prefix: &quot;language-&quot;, smartypants: false})

    &quot;https://thepid.de/blog/#{post.id}&quot;
    |&gt; Entry.new(updated_at, &quot;#{post.title}&quot;)
    |&gt; Entry.content(content, type: &quot;html&quot;)
    |&gt; Entry.published(inserted_at)
    |&gt; Entry.build()
  end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;build_feed&lt;/code&gt; function builds the feed with entries, created through the &lt;code&gt;get_entry&lt;/code&gt; function.
I decided to include the whole article as html in the feed, so you can read everything through your reader application.
Unfortunately this is not possible with some feeds out there.&lt;/p&gt;
&lt;p&gt;Now we have to serve the generated document at the /feed route.
Therefor just add this line to your router.ex&lt;/p&gt;
&lt;pre lang=&quot;Elixir&quot;&gt;&lt;code&gt;  scope &quot;/&quot;, ThePidWeb do
    pipe_through :browser

    get &quot;/feed&quot;, FeedController, :index
  end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The router will try to hit the feed controller with an index function.&lt;/p&gt;
&lt;pre lang=&quot;Elixir&quot;&gt;&lt;code&gt;defmodule ThePidWeb.FeedController do
  use ThePidWeb, :controller

  plug :put_layout, false
  plug :put_root_layout, false

  def index(conn, _assigns) do
    conn
    |&gt; put_resp_content_type(&quot;text/xml&quot;)
    |&gt; render(:feed)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here I specified the response type as text/xml and removed all layouts.
Finally the render functions expects a view with a feed function.
This is were the &lt;code&gt;build_feed&lt;/code&gt; function is finally called.&lt;/p&gt;
&lt;pre lang=&quot;Elixir&quot;&gt;&lt;code&gt;defmodule ThePidWeb.FeedHTML do
  use ThePidWeb, :html

  alias ThePid.Blog

  def feed(_assigns) do
    Blog.get_all_posts()
    |&gt; Blog.build_feed()
    |&gt; Phoenix.HTML.raw()
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s it. Easy peasy. Subscribe &lt;a href=&quot;https://thepid.de/feed&quot;&gt;now&lt;/a&gt;.&lt;/p&gt;</content>
    <link type="image" rel="enclosure" href=""/>
    <link type="text/html" rel="alternate" href="https://thepid.de/blog/4"/>
    <id>4</id>
    <title>How to add a feed to your elixir phoenix blog</title>
    <updated>2023-08-18T14:22:12+02:00</updated>
  </entry>
  <entry>
    <published>2023-09-08T11:35:07+02:00</published>
    <content type="html">&lt;p&gt;This year has been an exceptionally good for chili. As you may not know I like hot food and have become a passionate chili gardener and consumer.&lt;/p&gt;
&lt;h2&gt;The harvest&lt;/h2&gt;
&lt;p&gt;Unlike previous years I could harvest a lot of chilies already in July and August.
I think this is because I overwintered some plants and also build a raised bed, which dramatically accelerates the growth of the plants.
I grow several chili strains this year, including two different kinds of habaneros, scotch bonnets, lemon drop, and some others I forgot the name ;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://minio.mutualisten.de/thepid/IMG_20230713_200654.avif&quot; alt=&quot;Raised bed&quot; /&gt;
&lt;img src=&quot;https://minio.mutualisten.de/thepid/IMG_20230810_184349.avif&quot; alt=&quot;Plants&quot; /&gt;
&lt;img src=&quot;https://minio.mutualisten.de/thepid/IMG_20230810_184606.avif&quot; alt=&quot;Plants&quot; /&gt;
&lt;img src=&quot;https://minio.mutualisten.de/thepid/IMG_20230810_184631.avif&quot; alt=&quot;Plants&quot; /&gt;
&lt;img src=&quot;https://minio.mutualisten.de/thepid/IMG_20230812_174143.avif&quot; alt=&quot;Basket1&quot; /&gt;
&lt;img src=&quot;https://minio.mutualisten.de/thepid/IMG_20230826_184422.avif&quot; alt=&quot;Basket2&quot; /&gt;
&lt;img src=&quot;https://minio.mutualisten.de/thepid/IMG_20230902_131525.avif&quot; alt=&quot;Basket2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Preparation&lt;/h2&gt;
&lt;p&gt;The sauce I prepared this time was more or less free style with ingredients readily available in the garden and kitchen.
I like to combine chili with something fruity. This time I used figs from the garden and pears picked from some trees.&lt;/p&gt;
&lt;p&gt;Preparation is easy. Cut the chilies and fruits, roast or cook them for some minutes, add some vinegar and lemon juice and finally blend it!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://minio.mutualisten.de/thepid/IMG_20230902_135912.avif&quot; alt=&quot;Cooking&quot; /&gt;
&lt;img src=&quot;https://minio.mutualisten.de/thepid/IMG_20230902_142542.avif&quot; alt=&quot;Blend&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The result&lt;/h2&gt;
&lt;p&gt;Very tasty and also pretty hot.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://minio.mutualisten.de/thepid/IMG_20230903_103443.avif&quot; alt=&quot;Bottled&quot; /&gt;&lt;/p&gt;</content>
    <link type="image" rel="enclosure" href="https://minio.mutualisten.de/thepid/IMG_20230902_131525.avif"/>
    <link type="text/html" rel="alternate" href="https://thepid.de/blog/5"/>
    <id>5</id>
    <title>Hot chili sauce</title>
    <updated>2024-08-25T20:10:49+02:00</updated>
  </entry>
  <entry>
    <published>2024-06-16T22:03:56+02:00</published>
    <content type="html">&lt;p&gt;In the last months I added and also removed some content from the thepid.de. Three new pages are waiting for you to be explored.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;view &lt;a href=&quot;https://thepid.de/gardencam&quot;&gt;/gardencam&lt;/a&gt;&lt;/h1&gt;
&lt;h3&gt;content&lt;/h3&gt;
&lt;p&gt;Have a look in my garden and enjoy the green scenery. You can also see how the weather conditions are in the garden.&lt;/p&gt;
&lt;h3&gt;tech&lt;/h3&gt;
&lt;p&gt;Pictures are taken with a Logitech external webcam attached to a rpi4. &lt;a href=&quot;https://github.com/motioneye-project/motioneye&quot;&gt;motioneye&lt;/a&gt; is taking care of the webcam feed and shooting of the pictures. Pictures are uploaded to a self-hosted minio instance.
Temperature, humidity and pressure readings are from a &lt;a href=&quot;https://ruuvi.com/&quot;&gt;ruuvi&lt;/a&gt; tag, which is integrated in the home assistant instance running on the same rpi4. Home assistant sends the data to a self-hosted influxdb database, which is then queried from thepid.de&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;now &lt;a href=&quot;https://thepid.de/now&quot;&gt;/now&lt;/a&gt;&lt;/h1&gt;
&lt;h3&gt;content&lt;/h3&gt;
&lt;p&gt;I think this one is pretty self-explaining. Just have a look.&lt;/p&gt;
&lt;h3&gt;tech&lt;/h3&gt;
&lt;p&gt;Hard-coded for now.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;starred &lt;a href=&quot;https://thepid.de/starred&quot;&gt;/starred&lt;/a&gt;&lt;/h1&gt;
&lt;h3&gt;content&lt;/h3&gt;
&lt;p&gt;A list of blog posts, which I have starred in my feed reader. Probably pretty random, german, english, mixed topics. Not very curated at the moment. But I guess it will improve.&lt;/p&gt;
&lt;h3&gt;tech&lt;/h3&gt;
&lt;p&gt;Simple API call to my self-hosted miniflux instance. Love it.&lt;/p&gt;</content>
    <link type="image" rel="enclosure" href="https://minio.mutualisten.de/garden-cam/eggenstein_2024-08-19/19-30-00.jpg "/>
    <link type="text/html" rel="alternate" href="https://thepid.de/blog/6"/>
    <id>6</id>
    <title>Site update</title>
    <updated>2024-08-25T20:28:05+02:00</updated>
  </entry>
  <entry>
    <published>2024-08-25T21:03:36+02:00</published>
    <content type="html">&lt;p&gt;I am not the most passionate youtube consumer. I mainly watch educational videos (How to repair XY, how to do something), but there are a few exceptions of course. Both recommendations here are german and I know the creators, so I might be a bit biased.&lt;/p&gt;
&lt;h2&gt;Pfadgefährten&lt;/h2&gt;
&lt;p&gt;Hannes and Tobi are hiking with different animals in the Kyrgyz mountains. Right now only the trailer is available, but seems like a great adventure. Also Hannes is my landlord and he is a great guy, who knows how to tell stories.
&lt;a href=&quot;https://www.youtube.com/watch?v=7cqSH3xFACo&quot;&gt;Trailer&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Sailing Lea&lt;/h2&gt;
&lt;p&gt;Johannes and Olena are sailing around the world with their yacht. Great pictures and the relaxed sailor lifestyle are making me quite jealous. I can connect, as I also enjoy sailing and I have briefly met Johannes two or three times.
&lt;a href=&quot;https://www.youtube.com/channel/UC4kZU4Pb0CpSw_WnPCYpGaA&quot;&gt;Channel&lt;/a&gt;&lt;/p&gt;</content>
    <link type="image" rel="enclosure" href="https://i.ytimg.com/vi/7cqSH3xFACo/hq720.jpg?sqp=-oaymwEhCK4FEIIDSFryq4qpAxMIARUAAAAAGAElAADIQj0AgKJD&amp;rs=AOn4CLDSoUNtWXHXLuFxtznJ-1GdIOCcSQ"/>
    <link type="text/html" rel="alternate" href="https://thepid.de/blog/7"/>
    <id>7</id>
    <title>Pfadgefährten &amp; Sailing Lea</title>
    <updated>2024-08-25T22:31:02+02:00</updated>
  </entry>
  <entry>
    <published>2024-11-07T20:40:10+01:00</published>
    <content type="html">&lt;p&gt;Today I want to share a small little tool, which converts your favorite images and wallpapers with a given color palette.
This makes it inedible easy to create a consist styling. And your style is everything.&lt;/p&gt;
&lt;p&gt;The tool is called &lt;a href=&quot;https://github.com/doprz/dipc&quot;&gt;dipc&lt;/a&gt; and is of course written in Rust.
Give it a try!&lt;/p&gt;
&lt;p&gt;I used the following settings for the header image.&lt;/p&gt;
&lt;pre lang=&quot;bash&quot;&gt;&lt;code&gt;dipc -s &quot;Dark mode&quot; gruvbox original_image.jpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Btw. do you know which delicious fungi is featured in the header?&lt;/p&gt;</content>
    <link type="image" rel="enclosure" href=""/>
    <link type="text/html" rel="alternate" href="https://thepid.de/blog/8"/>
    <id>8</id>
    <title>New header image with the power of dipc</title>
    <updated>2024-11-07T20:43:57+01:00</updated>
  </entry>
  <entry>
    <published>2025-02-04T07:04:27+01:00</published>
    <content type="html">&lt;p&gt;I made a little spinoff of the blog to write about traveling with Selmi. It is in German.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://thepid.de/travel&quot;&gt;Enjoy&lt;/a&gt;&lt;/p&gt;</content>
    <link type="image" rel="enclosure" href="https://thepid.de/images/header/travelheader.webp"/>
    <link type="text/html" rel="alternate" href="https://thepid.de/blog/11"/>
    <id>11</id>
    <title>Blog Spinoff</title>
    <updated>2025-02-04T07:06:22+01:00</updated>
  </entry>
  <entry>
    <published>2025-07-27T10:54:10+02:00</published>
    <content type="html">&lt;p&gt;One of the most frequent and practical uses for note-taking tools in our daily life is managing a &lt;strong&gt;shared grocery list&lt;/strong&gt;. I like to keep and update the list with Selma — often &lt;strong&gt;simultaneously&lt;/strong&gt;, while one of us is already in the store.&lt;/p&gt;
&lt;p&gt;Over time, we tried several approaches to make this work smoothly, especially with &lt;strong&gt;self-hosted&lt;/strong&gt; tools. We first used classical note-taking apps like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://joplinapp.org/&quot;&gt;Joplin&lt;/a&gt; — which I already self-host&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.appflowy.io/&quot;&gt;AppFlowy&lt;/a&gt; — a promising local-first Notion alternative&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unfortunately, we ran into &lt;strong&gt;sync issues&lt;/strong&gt; with both. Items would go missing, checkboxes wouldn’t update correctly, or changes wouldn’t appear on the other person’s device in real-time. It became clear we needed something more tailored for &lt;strong&gt;real-time, shared editing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Eventually, we settled on a tool called &lt;a href=&quot;https://github.com/davideshay/groceries&quot;&gt;&lt;strong&gt;specifically clementines&lt;/strong&gt;&lt;/a&gt; (previously named &lt;em&gt;grocies&lt;/em&gt;). It&apos;s a minimalist web app designed exactly for this use case — keeping a shared, synced grocery list.&lt;/p&gt;
&lt;p&gt;Technically, &lt;strong&gt;specifically clementines&lt;/strong&gt; is built on &lt;strong&gt;CouchDB&lt;/strong&gt;, a document-oriented NoSQL database that supports &lt;strong&gt;multi-master replication&lt;/strong&gt;. This means changes from multiple clients can be merged automatically — ideal for shared lists edited from different locations and devices at the same time. It also makes offline-first usage possible.&lt;/p&gt;
&lt;p&gt;Setting it up was a bit involved: in my case, it needed &lt;strong&gt;three dedicated subdomains&lt;/strong&gt; (for the app backend, frontend and CouchDB), but once deployed, it has worked &lt;strong&gt;as expected&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If you&apos;re also looking for a reliable, real-time shared list app and don&apos;t mind a little self-hosting work, I can highly recommend giving it a try.&lt;/p&gt;</content>
    <link type="image" rel="enclosure" href="https://grcs.mutualisten.de/assets/icon/favicon.svg"/>
    <link type="text/html" rel="alternate" href="https://thepid.de/blog/12"/>
    <id>12</id>
    <title>The Quest for a Shared Grocery List App</title>
    <updated>2025-07-27T10:54:10+02:00</updated>
  </entry>
  <entry>
    <published>2025-10-02T12:34:13+02:00</published>
    <content type="html">&lt;p&gt;For years I’ve used InfluxDB as my main database for logging all kinds of time-based data. It’s reliable, lightweight, and well-suited for storing metrics or events. One of the more personal data streams I collect is my own location history, captured through &lt;a href=&quot;https://owntracks.org/&quot;&gt;OwnTracks&lt;/a&gt; on Android. The app continuously sends geo-coordinates to my server, where they’re stored in InfluxDB.&lt;/p&gt;
&lt;p&gt;Until now, my usage was simple: I would pull up a map view of a single day or a couple of weeks and see where I had been. It was more about having a “quantified self” archive than about running complex analysis. Recently, though, I started asking: &lt;em&gt;what’s possible if I treat this data more seriously?&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Setting up Geo-Temporal Data in InfluxDB&lt;/h2&gt;
&lt;p&gt;OwnTracks logs latitude/longitude points every few minutes. In my case:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Around &lt;strong&gt;91,000 points per year&lt;/strong&gt; (every 6 minutes).&lt;/li&gt;
&lt;li&gt;A single day of data is easy to visualize, but over longer time spans, performance matters.&lt;/li&gt;
&lt;li&gt;I wanted to test whether InfluxDB’s &lt;strong&gt;S2 index&lt;/strong&gt; could make queries faster when filtering spatially. Thanks &lt;a href=&quot;https://talfus-laddus.de/&quot;&gt;Matthias&lt;/a&gt; for the hint :)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The S2 index is a way of mapping latitude/longitude coordinates onto a hierarchical cell system that’s easier to query than raw floats. Instead of searching through raw coordinates, InfluxDB can quickly filter based on cell IDs.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Performance Benchmark&lt;/h2&gt;
&lt;p&gt;I compared two setups:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;With &lt;code&gt;s2_cell_id&lt;/code&gt; present&lt;/strong&gt;: data stored with precomputed S2 cell IDs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;On the fly&lt;/strong&gt;: InfluxDB computes S2 cells during query time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dataset: one year of data (~91k points).&lt;br /&gt;
Typical filter returned ~2,300 points (≈2.5%).&lt;/p&gt;
&lt;h3&gt;Results&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query type&lt;/th&gt;
&lt;th&gt;With S2 index&lt;/th&gt;
&lt;th&gt;On the fly&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Raw data (wide form)&lt;/td&gt;
&lt;td&gt;7.18s&lt;/td&gt;
&lt;td&gt;13.1s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter @ level 10&lt;/td&gt;
&lt;td&gt;7.49s&lt;/td&gt;
&lt;td&gt;20.48s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter @ level 11&lt;/td&gt;
&lt;td&gt;8.28s&lt;/td&gt;
&lt;td&gt;21.37s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter @ level 12&lt;/td&gt;
&lt;td&gt;11.16s&lt;/td&gt;
&lt;td&gt;24.05s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Observations&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Even with S2 indexing, &lt;strong&gt;base query cost dominates&lt;/strong&gt;: fetching raw data is the main bottleneck.&lt;/li&gt;
&lt;li&gt;The index does help: filtering is roughly &lt;strong&gt;2–3× faster&lt;/strong&gt; compared to on-the-fly calculations.&lt;/li&gt;
&lt;li&gt;Increasing resolution (higher S2 levels) increases query time, so there’s a trade-off between spatial precision and performance.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;A Short Note on S2 Geometry&lt;/h2&gt;
&lt;p&gt;S2 is a geometry library originally developed at Google. Its core idea is to represent the Earth’s surface by projecting it onto the faces of a cube, which is then subdivided recursively into cells by using Hilbert curves.  &lt;a href=&quot;https://s2geometry.io/&quot;&gt;S2 Geometry&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each &lt;strong&gt;S2 cell&lt;/strong&gt; is identified by a unique integer ID.&lt;/li&gt;
&lt;li&gt;Cells can be subdivided hierarchically, so you can choose different &lt;strong&gt;levels of resolution&lt;/strong&gt; (coarse regions vs. fine-grained areas).&lt;/li&gt;
&lt;li&gt;This makes spatial queries (like “points within this area”) much faster than comparing raw lat/long values.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short: S2 turns messy geo data into something databases can index efficiently.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;And now&lt;/h2&gt;
&lt;p&gt;So far, my experiments confirm that S2 indexing is useful for speeding up geo-temporal queries in InfluxDB, but the raw data size and query design still matter a lot.&lt;/p&gt;
&lt;p&gt;For my personal use case — reviewing trips, walks, and places I’ve been — the current performance is good enough. But this exploration opens doors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Building &lt;strong&gt;aggregate views&lt;/strong&gt; (e.g. heatmaps of most-visited areas).&lt;/li&gt;
&lt;li&gt;Running &lt;strong&gt;long-term analyses&lt;/strong&gt; (seasonal mobility, time spent in places).&lt;/li&gt;
&lt;li&gt;Integrating with visualization tools beyond simple day-by-day maps.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;InfluxDB, OwnTracks, and S2 indexing give me an okayish base. The next step is to think less like a log collector and more like a geospatial analyst. Well the last sentence I would not have written myself. Otherwise I am good creating the blog post with the help of ChatGPT.&lt;/p&gt;</content>
    <link type="image" rel="enclosure" href="https://s2geometry.io/devguide/img/s2hierarchy.gif"/>
    <link type="text/html" rel="alternate" href="https://thepid.de/blog/13"/>
    <id>13</id>
    <title>Exploring Geo-Temporal Data in InfluxDB</title>
    <updated>2025-10-02T12:37:28+02:00</updated>
  </entry>
  <entry>
    <published>2026-08-07T20:31:11+02:00</published>
    <content type="html">&lt;h1&gt;Tile Hunting with Open-Source Tools&lt;/h1&gt;
&lt;p&gt;Tile hunting adds a simple exploration game to cycling, walking, running, and other outdoor activities. A map is divided into a fixed grid, and every tile crossed by one of my recorded GPS tracks is marked as explored.&lt;/p&gt;
&lt;p&gt;This creates a different motivation from conventional activity statistics. Distance, speed, and elevation still matter, but tile hunting also rewards curiosity. A single missing tile can be enough reason to leave a familiar route, investigate a side road, or visit a nearby place that I have never seen before.&lt;/p&gt;
&lt;h2&gt;Two sizes of tiles&lt;/h2&gt;
&lt;p&gt;The grid is based on the same &lt;strong&gt;Web Mercator tile system&lt;/strong&gt; commonly used by online maps. At every zoom level, the world is divided into more tiles: increasing the zoom level by one splits every tile into four smaller tiles—two in each direction.&lt;/p&gt;
&lt;p&gt;I collect tiles at two zoom levels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zoom level 14 (&lt;code&gt;z14&lt;/code&gt;)&lt;/strong&gt; for larger-scale exploration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zoom level 17 (&lt;code&gt;z17&lt;/code&gt;)&lt;/strong&gt; for detailed local exploration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At the latitude of Germany, a &lt;code&gt;z14&lt;/code&gt; tile is roughly &lt;strong&gt;1.5 kilometres wide&lt;/strong&gt;. This makes it a useful size for cycling: the tiles are small enough to encourage exploration but large enough that a substantial area can be covered during a bicycle ride.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;z17&lt;/code&gt; grid is considerably finer. There are three zoom levels between &lt;code&gt;z14&lt;/code&gt; and &lt;code&gt;z17&lt;/code&gt;, and every additional level halves the width of a tile. A &lt;code&gt;z17&lt;/code&gt; tile is therefore one eighth as wide as a &lt;code&gt;z14&lt;/code&gt; tile, or roughly &lt;strong&gt;190 metres across&lt;/strong&gt; in Germany.&lt;/p&gt;
&lt;p&gt;In terms of area, one &lt;code&gt;z14&lt;/code&gt; tile contains an ( 8 \times 8 ) grid of &lt;code&gt;z17&lt;/code&gt; tiles:&lt;/p&gt;
&lt;p&gt;[
8 \times 8 = 64
]&lt;/p&gt;
&lt;p&gt;The finer &lt;code&gt;z17&lt;/code&gt; tiles are sometimes called &lt;strong&gt;squadratinhos&lt;/strong&gt;. They work particularly well for walking and for detailed exploration close to home. While a bicycle ride can quickly cross a large number of &lt;code&gt;z14&lt;/code&gt; tiles, filling the &lt;code&gt;z17&lt;/code&gt; grid may require visiting individual streets, paths, parks, and corners of a neighbourhood.&lt;/p&gt;
&lt;h2&gt;When is a tile explored?&lt;/h2&gt;
&lt;p&gt;The basic rule is deliberately simple: a tile counts as explored when at least one recorded activity enters it.&lt;/p&gt;
&lt;p&gt;Once a track enters a tile, that tile becomes part of my explored map. It does not matter how far I travel through it—a short visit across one corner is sufficient.&lt;/p&gt;
&lt;p&gt;This simplicity occasionally makes route planning more interesting than expected. A road may appear to pass through a tile but remain just outside its boundary. Conversely, a small detour of only a few metres may be enough to collect it.&lt;/p&gt;
&lt;h2&gt;Building a cluster&lt;/h2&gt;
&lt;p&gt;Collecting isolated tiles is enjoyable, but the more interesting goal for me is to create a large connected area.&lt;/p&gt;
&lt;p&gt;The software I use distinguishes between an &lt;strong&gt;explored tile&lt;/strong&gt; and a &lt;strong&gt;cluster tile&lt;/strong&gt;. An explored tile becomes part of the cluster calculation when all four of its direct neighbours have also been explored:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the tile to the north,&lt;/li&gt;
&lt;li&gt;the tile to the south,&lt;/li&gt;
&lt;li&gt;the tile to the east,&lt;/li&gt;
&lt;li&gt;and the tile to the west.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Diagonal neighbours do not count.&lt;/p&gt;
&lt;p&gt;This means that simply drawing a narrow line of explored tiles through the landscape does not create a large cluster. To activate the tiles along that line, I also need to explore the surrounding tiles. The result rewards broad, continuous coverage rather than isolated routes.&lt;/p&gt;
&lt;p&gt;Adjacent cluster tiles are grouped into connected clusters. A single missing tile can prevent parts of an explored area from joining together, while filling one strategically placed gap may connect several smaller clusters into a much larger one.&lt;/p&gt;
&lt;p&gt;That is why gaps become particularly tempting. On the map, they are immediately visible as holes in an otherwise explored region. Filling them often becomes the objective of my next ride.&lt;/p&gt;
&lt;h2&gt;Growing the maximum square&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;maximum square&lt;/strong&gt; is a related but even stricter challenge. It is the largest completely explored square that can be placed on the tile grid.&lt;/p&gt;
&lt;p&gt;A maximum square of size ( n ) must contain ( n \times n ) explored tiles without any gaps. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a square of size 5 contains 25 tiles,&lt;/li&gt;
&lt;li&gt;a square of size 10 contains 100 tiles,&lt;/li&gt;
&lt;li&gt;and a square of size 20 contains 400 tiles.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Increasing a square from size ( n ) to size ( n + 1 ) becomes progressively harder. It requires adding an entire row and an entire column around the existing square:&lt;/p&gt;
&lt;p&gt;[
(n + 1)^2 - n^2 = 2n + 1
]&lt;/p&gt;
&lt;p&gt;Growing a &lt;code&gt;10 × 10&lt;/code&gt; square into an &lt;code&gt;11 × 11&lt;/code&gt; square therefore requires at least 21 additional tiles—and possibly more if the square has to move in a different direction to avoid inaccessible areas.&lt;/p&gt;
&lt;p&gt;The square must also remain completely filled. One missing tile caused by a river, railway, motorway, military area, industrial site, or private property can block expansion in that direction. This turns the maximum square into both an exploration challenge and a routing puzzle.&lt;/p&gt;
&lt;p&gt;In practice, the cluster and maximum square encourage slightly different strategies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Total explored tiles&lt;/strong&gt; reward going anywhere new.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The cluster&lt;/strong&gt; rewards connecting and filling a wider region.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The maximum square&lt;/strong&gt; rewards systematic and gap-free exploration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I mainly use the &lt;code&gt;z14&lt;/code&gt; grid for bicycle rides and for extending my regional cluster and maximum square. The much finer &lt;code&gt;z17&lt;/code&gt; grid provides a separate challenge for local exploration, where individual streets and paths make a visible difference.&lt;/p&gt;
&lt;p&gt;Commercial platforms such as Squadrats and VeloViewer provide similar functionality. My setup, however, is based on open-source tools and keeps the activity data under my control.&lt;/p&gt;
&lt;h2&gt;Recording and importing activities&lt;/h2&gt;
&lt;p&gt;Most of my activity data comes from Garmin Connect. I record walks and other activities with a &lt;strong&gt;Garmin Instinct 2&lt;/strong&gt;, while bicycle rides are recorded with a &lt;strong&gt;Garmin Edge Explore 2&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To retrieve the original activity files from Garmin Connect, I use &lt;a href=&quot;https://github.com/tcgoetz/GarminDB&quot;&gt;GarminDB&lt;/a&gt;. Besides downloading activity files, GarminDB can import Garmin health and activity data into local SQLite databases. Importantly, it retains the downloaded FIT and JSON files, so they can be processed again without repeatedly downloading them from Garmin Connect.&lt;/p&gt;
&lt;p&gt;These activity files are then imported into &lt;a href=&quot;https://martin-ueding.github.io/geo-activity-playground/&quot;&gt;Geo Activity Playground&lt;/a&gt;, a self-hosted application for exploring GPS tracks. It accepts several common formats, including FIT, GPX, TCX, KML, and CSV.&lt;/p&gt;
&lt;p&gt;Geo Activity Playground provides much more than tile hunting—such as heatmaps, activity statistics, equipment tracking, and yearly summaries—but its &lt;strong&gt;Explorer Tiles&lt;/strong&gt; are the feature that interests me most.&lt;/p&gt;
&lt;h2&gt;Planning routes to unexplored tiles&lt;/h2&gt;
&lt;p&gt;Knowing which tiles are missing is only half the job. I also need a convenient way to plan routes through them.&lt;/p&gt;
&lt;p&gt;Geo Activity Playground exposes the explored tiles through a map tile server. I add that server as a custom overlay in &lt;a href=&quot;https://bikerouter.de/&quot;&gt;Bikerouter&lt;/a&gt;, my preferred tool for planning bicycle routes.&lt;/p&gt;
&lt;p&gt;Bikerouter supports custom tile layers, so my explored area can be displayed directly on top of the routing map. I can immediately see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;which tiles I have already visited,&lt;/li&gt;
&lt;li&gt;where there are gaps in my cluster,&lt;/li&gt;
&lt;li&gt;and which roads could connect new tiles to the existing area.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From there, I plan a route that passes through the missing tiles, export it, and transfer it to my bicycle computer. After completing the ride, the new Garmin activity passes through the same processing pipeline and the additional tiles appear on the map.&lt;/p&gt;
&lt;p&gt;The result is a satisfying loop:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Record&lt;/strong&gt; an activity with a Garmin device.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extract&lt;/strong&gt; the original activity file using GarminDB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Import&lt;/strong&gt; it into Geo Activity Playground.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inspect&lt;/strong&gt; the newly explored tiles.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan&lt;/strong&gt; the next ride in Bikerouter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extend&lt;/strong&gt; the connected cluster.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Displaying the results on my website&lt;/h2&gt;
&lt;p&gt;The tile server is not limited to route planning. I also use it to display my explored area on the &lt;a href=&quot;/tiles&quot;&gt;&lt;code&gt;/tiles&lt;/code&gt;&lt;/a&gt; page of this website.&lt;/p&gt;
&lt;p&gt;The map itself loads the tiles exposed by Geo Activity Playground. Additional statistics are queried through &lt;strong&gt;Datasette&lt;/strong&gt;, which provides a web interface and JSON API for the underlying SQLite data.&lt;/p&gt;
&lt;p&gt;This is currently a small workaround: Geo Activity Playground does not yet expose the statistics I need through a dedicated API. Datasette lets me access them without introducing a separate application or duplicating the data.&lt;/p&gt;
&lt;h2&gt;Why I prefer this setup&lt;/h2&gt;
&lt;p&gt;The setup has more moving parts than registering with a commercial tile-hunting service, but it also gives me considerably more control:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My original activity files remain available locally.&lt;/li&gt;
&lt;li&gt;The processing is performed with open-source software.&lt;/li&gt;
&lt;li&gt;I am not tied to a particular activity platform.&lt;/li&gt;
&lt;li&gt;The tile layer can be used in different maps and routing tools.&lt;/li&gt;
&lt;li&gt;I can publish the map and statistics on my own website.&lt;/li&gt;
&lt;li&gt;I can adapt the workflow when my requirements change.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most importantly, tile hunting gives me a reason to explore. A small gap in the cluster can turn an ordinary bicycle ride into a route-planning puzzle—and often leads me to interesting places surprisingly close to home.&lt;/p&gt;</content>
    <link type="image" rel="enclosure" href="https://cf.veloviewer.com/blog/ClusterDetail.jpg"/>
    <link type="text/html" rel="alternate" href="https://thepid.de/blog/14"/>
    <id>14</id>
    <title>Tile Hunting</title>
    <updated>2026-08-07T20:31:11+02:00</updated>
  </entry>
</feed>