<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Patrick Stival</title>
    <description>The latest articles on DEV Community by Patrick Stival (@trickstival).</description>
    <link>https://dev.to/trickstival</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F316342%2Fafffd123-bc9e-4107-81cc-c1a85ec83e82.jpg</url>
      <title>DEV Community: Patrick Stival</title>
      <link>https://dev.to/trickstival</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/trickstival"/>
    <language>en</language>
    <item>
      <title>Understanding Geohashes</title>
      <dc:creator>Patrick Stival</dc:creator>
      <pubDate>Mon, 24 Jul 2023 21:31:03 +0000</pubDate>
      <link>https://dev.to/trickstival/understanding-geohashes-3kb</link>
      <guid>https://dev.to/trickstival/understanding-geohashes-3kb</guid>
      <description>&lt;p&gt;Hello there.&lt;/p&gt;

&lt;p&gt;Are you curious about how geographic locations can be represented as a short string of letters and numbers? Or how this technique helps in fast retrieval of location data?&lt;/p&gt;

&lt;p&gt;This post will take you on a deep dive into geohashes, explaining what they are, how they work, and their practical uses. Whether you're a data scientist, a software engineer, or just someone with a knack for geography and data, this post will help you grasp the concept of geohashes and their significance.&lt;/p&gt;

&lt;p&gt;The purpose of this post is to explain in depth how a geohash works and its subtleties step-by-step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a geohash?
&lt;/h2&gt;

&lt;p&gt;Chat GPT says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A geohash is a location encoding system that represents a geographic location using a short string of letters and numbers. It works by dividing the world into a grid of squares, assigning each square a unique hash. The length of the hash controls the precision, with longer hashes corresponding to smaller, more precise squares.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That gives us a general idea. You use a geohash to identify areas on a map. The easiest way to visualize them is with an &lt;a href="https://geohash.softeng.co/"&gt;interactive map&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2YP57xNz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yytikwt157cts9gv0nm7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2YP57xNz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yytikwt157cts9gv0nm7.png" alt="Map with precision 1 geohashes" width="800" height="791"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why geohashes
&lt;/h2&gt;

&lt;p&gt;Databases are better optimized for text than geolocation queries. If you need fast retrieval over a large collection of data, &lt;a href="https://en.wikipedia.org/wiki/R-tree"&gt;R-Tree&lt;/a&gt; indexes might not be enough.&lt;/p&gt;

&lt;p&gt;Quoting &lt;a href="https://chrishewett.com/blog/geohash-explorer/"&gt;Chris Hewett's website&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Geohash avoids/minimises the use of (usually) slow geospacial functions. Who you going to call when your ST_CONTAINS() is slow and an EXPLAIN shows it's already using an index...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Also, geohashes can be used as an alternative to &lt;a href="https://developers.google.com/maps/documentation/javascript/coordinates"&gt;tile coordinates&lt;/a&gt; in order to load chunks of a map and possibly cache them on the client side.&lt;/p&gt;
&lt;h2&gt;
  
  
  Different precisions
&lt;/h2&gt;

&lt;p&gt;As chat gpt said, a geohash length describes its precision. For instance, &lt;code&gt;dr7&lt;/code&gt; is a geohash with precision 3. That means that its parent, &lt;code&gt;dr&lt;/code&gt; (precision 2), encloses a larger area that also contains its neighbors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6v4oBFN9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kd1fuldtksgojibwoyv7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6v4oBFN9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kd1fuldtksgojibwoyv7.png" alt="dr7 geohash on the map" width="800" height="1159"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  2 types of geohashes
&lt;/h2&gt;

&lt;p&gt;There are 2 types of geohash grids (width x height): &lt;code&gt;8x4&lt;/code&gt; and &lt;code&gt;4x8&lt;/code&gt;. That means that for any given zoom level, there are &lt;code&gt;32&lt;/code&gt; possible squares inside.&lt;/p&gt;

&lt;p&gt;We start with an &lt;code&gt;8x4&lt;/code&gt; grid with the whole world inside. Each time you go one precision deeper, the type of grid you get alternates. This is made this way, because if we kept grids in the same format for all zoom levels, we would eventually get super thin areas.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7KADq_A7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yak5ges4poussukkj4mq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7KADq_A7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yak5ges4poussukkj4mq.gif" alt="precision levels alternating on the map" width="800" height="610"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since there are &lt;code&gt;8x4&lt;/code&gt; and &lt;code&gt;4x8&lt;/code&gt; grids only, we use these 32 characters to encode each precision: &lt;code&gt;0123456789bcdefghjkmnpqrstuvwxyz&lt;/code&gt;.&lt;br&gt;
They range from 0 to z, but beware, there are a few letters missing in this specific encoding, such as &lt;code&gt;a;i;l and o&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Keep dividing by 2
&lt;/h2&gt;

&lt;p&gt;If you want to find out the geohash in which a point lies into, you have to bisect it &lt;code&gt;5&lt;/code&gt; times. Why 5? Because &lt;code&gt;2^5&lt;/code&gt; is &lt;code&gt;32&lt;/code&gt;, which is the total amount of geohashes for each precision level. &lt;/p&gt;

&lt;p&gt;Let's do a dry run of the algorithm to figure out in which geohash1 a location &lt;code&gt;L&lt;/code&gt; in argentina lies into.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;purple: divide the world in 2. &lt;code&gt;L&lt;/code&gt; is in the first half&lt;/li&gt;
&lt;li&gt;red: &lt;code&gt;L&lt;/code&gt; is beneath&lt;/li&gt;
&lt;li&gt;orange: &lt;code&gt;L&lt;/code&gt; is to the right&lt;/li&gt;
&lt;li&gt;yellow: &lt;code&gt;L&lt;/code&gt; is above&lt;/li&gt;
&lt;li&gt;green: &lt;code&gt;L&lt;/code&gt; is to the left&lt;/li&gt;
&lt;li&gt;We have run 5 tests already, so the geohash is &lt;code&gt;6&lt;/code&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u82kWbbA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5tdso2kczfbjn9ipon06.png" alt="dry run illustration" width="800" height="798"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice that we always have two possible moves for each step: to choose between the first and the second half. The moves alternate between latitude and longitude, so we are able to move in 2D.&lt;/p&gt;

&lt;p&gt;Also, we have to somehow translate the accumulated result of those moves into an index for the string &lt;code&gt;0123456789bcdefghjkmnpqrstuvwxyz&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The rules for that are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We start with an index valued 0&lt;/li&gt;
&lt;li&gt;If the location lies in the first half (lower than mid), multiply the index by 2&lt;/li&gt;
&lt;li&gt;If it's the second half (higher than mid), multiply the index by 2 and add 1&lt;/li&gt;
&lt;li&gt;Repeat 4 more times&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means we are using even numbers to identify &lt;code&gt;first-half&lt;/code&gt; moves and odd numbers for &lt;code&gt;second-half&lt;/code&gt; moves.&lt;/p&gt;

&lt;p&gt;The highest value we can get is 31:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 * 2 + 1 = 1
1 * 2 + 1 = 3
3 * 2 + 1 = 7
7 * 2 + 1 = 15
15 * 2 + 1 = 31
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since our index starts at 0, that would be 32 possibilities.&lt;/p&gt;

&lt;p&gt;This is exactly what the code is doing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// (c) Chris Veness 2014-2019&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;latMin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;latMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;lonMin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lonMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;geohash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evenBit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// bisect E-W longitude&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lonMid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lonMin&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;lonMax&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lon&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;lonMid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="nx"&gt;lonMin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lonMid&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
      &lt;span class="nx"&gt;lonMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lonMid&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// bisect N-S latitude&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;latMid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;latMin&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;latMax&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;latMid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="nx"&gt;latMin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;latMid&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
      &lt;span class="nx"&gt;latMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;latMid&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;evenBit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;evenBit&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="nx"&gt;bit&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 5 bits gives us a character: append it and start over&lt;/span&gt;
    &lt;span class="nx"&gt;geohash&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;base32&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;bit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&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="nx"&gt;geohash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we stop once the precision is met.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives to geohash
&lt;/h2&gt;

&lt;p&gt;If you check the &lt;a href="https://h3geo.org/docs#comparisons"&gt;h3geo comparison page&lt;/a&gt;, you should see plenty of alternatives to geohash, such as &lt;a href="https://s2geometry.io/"&gt;s2&lt;/a&gt; or even h3 itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Glossary
&lt;/h2&gt;

&lt;p&gt;ST_CONTAINS: &lt;a href="https://postgis.net/docs/ST_Contains.html"&gt;postgis function&lt;/a&gt; that finds geometries inside areas;&lt;br&gt;
EXPLAIN: &lt;a href="https://www.postgresql.org/docs/current/sql-explain.html"&gt;postgres statement&lt;/a&gt; that calculates query cost&lt;/p&gt;

</description>
      <category>geohash</category>
      <category>gis</category>
      <category>map</category>
    </item>
    <item>
      <title>Habbo: Avatar Rendering Basics</title>
      <dc:creator>Patrick Stival</dc:creator>
      <pubDate>Fri, 18 Sep 2020 22:49:42 +0000</pubDate>
      <link>https://dev.to/trickstival/habbo-avatar-rendering-basics-4cg6</link>
      <guid>https://dev.to/trickstival/habbo-avatar-rendering-basics-4cg6</guid>
      <description>&lt;p&gt;Hey there!&lt;br&gt;
In this article, I'm going to explain how you can render a pretty figure like this one:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyo6rt5at4hk6qea6ir7l.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyo6rt5at4hk6qea6ir7l.jpeg" alt="angry"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The avatar images in this article were rendered using &lt;a href="https://github.com/open-hotel/open-hotel-client/tree/structure-migration" rel="noopener noreferrer"&gt;Open Hotel Client&lt;/a&gt;. If you like habbo or games development in general, please consider contributing to the project or joining the team. My e-mail is at the bottom of this article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Oh right, and it can also walk, swim, lay and face 8 different positions! Sounds like a lot of work, and it's indeed something susceptible to a bunch of edge cases.&lt;/p&gt;

&lt;p&gt;The goal here is to learn how &lt;a href="https://habbo.com" rel="noopener noreferrer"&gt;Habbo&lt;/a&gt; handles their assets to build an avatar.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fa65bp39uo5lmusehs138.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fa65bp39uo5lmusehs138.jpeg" alt="std"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Naming convention
&lt;/h1&gt;

&lt;p&gt;An avatar figure is built using a combination of multiple body and clothing parts. You can use the &lt;a href="http://labs.habox.org/generator-avatar" rel="noopener noreferrer"&gt;habbox standalone avatar imager&lt;/a&gt; to try some combinations and get a string that describes your character.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fksfonmu38pgok4z1mxnl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fksfonmu38pgok4z1mxnl.png" alt="habbox"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At Open Hotel, we provide the same options as the habbox standalone imager for the avatar rendering (which is also &lt;a href="https://www.habbo.com/habbo-imaging/avatarimage?figure=hd-180-1" rel="noopener noreferrer"&gt;the default habbo api pattern&lt;/a&gt;). &lt;a href="https://github.com/open-hotel/open-hotel-client/blob/structure-migration/src/ui/views/game-page.vue#L44" rel="noopener noreferrer"&gt;Those&lt;/a&gt; are:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;look&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hd-180-1.hr-110-61.ch-210-66.lg-280-110.sh-305-62&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mv,respect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;head_direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;
  
  
  Figure parts
&lt;/h3&gt;

&lt;p&gt;In this example, our encoded avatar figure is:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

hd-195-1.hr-679-61.ha-1012-110.ch-804-1341.lg-275-110.sh-3089-110


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Each figure part is separated by a &lt;code&gt;.&lt;/code&gt;, and each one of these parts can be described as:&lt;br&gt;
&lt;code&gt;figureType-imageID-colorID1-colorID2...-colorIDn&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Actions
&lt;/h3&gt;

&lt;p&gt;Actions change the way we build the avatar figure. Take by example the std, laugh and mv actions:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fsyjropnmt0xc6u0sxxh0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fsyjropnmt0xc6u0sxxh0.png" alt="actions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that multiple actions can occur at the same time, like sitting and waving. According to the action applied, some body parts might stay the same, while others are changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Figure Parts Example
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Combining parts
&lt;/h3&gt;

&lt;p&gt;Let's try to render them separately at Open Hotel and see what we get:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;hd-195-1&lt;/code&gt;: Body + face with key &lt;code&gt;195&lt;/code&gt; and color &lt;code&gt;1&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fukw3tcndyj6xq07w8wuz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fukw3tcndyj6xq07w8wuz.png" alt="hd"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;hr-679-61&lt;/code&gt;: Hair with key &lt;code&gt;1012&lt;/code&gt; and color &lt;code&gt;61&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzopzfd5b61jb1v623vw4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzopzfd5b61jb1v623vw4.png" alt="hr"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ha-1012-110&lt;/code&gt;: Hat with key &lt;code&gt;1012&lt;/code&gt; and color &lt;code&gt;110&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fa88wxv6krihz41ew1dvd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fa88wxv6krihz41ew1dvd.png" alt="ha"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ch-804-1341&lt;/code&gt;: Shirt with key &lt;code&gt;804&lt;/code&gt; and color &lt;code&gt;1341&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7o0oi7ysigqazk6bgkav.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7o0oi7ysigqazk6bgkav.png" alt="ch"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;lg-275-110&lt;/code&gt;: Trousers with key &lt;code&gt;275&lt;/code&gt; and color &lt;code&gt;110&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8yuajtah3sf9cqy6dw0l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8yuajtah3sf9cqy6dw0l.png" alt="lg"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;code&gt;sh-3089-110&lt;/code&gt;: Shoes with key &lt;code&gt;3089&lt;/code&gt; and color &lt;code&gt;110&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fw5ymuxr2jvbl0yb2pay0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fw5ymuxr2jvbl0yb2pay0.png" alt="sh"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All figures combined:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftqk87onq4qkizuyvoogz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftqk87onq4qkizuyvoogz.png" alt="all"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice: the figure part &lt;code&gt;key&lt;/code&gt; is different from the figure part &lt;code&gt;id&lt;/code&gt;, as you can see in the &lt;code&gt;Figure Data&lt;/code&gt; section of this article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Take a look below at the hairs &lt;code&gt;hr-110-61&lt;/code&gt;, &lt;code&gt;hr-677-61&lt;/code&gt;, &lt;code&gt;hr-3048-61&lt;/code&gt;, &lt;code&gt;hr-165-61&lt;/code&gt;. All of them with color &lt;code&gt;61&lt;/code&gt;:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcny55x6lz7w0hhjczw9x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcny55x6lz7w0hhjczw9x.png" alt="hairs"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h1&gt;
  
  
  Figure Data
&lt;/h1&gt;

&lt;p&gt;Open Hotel provides a file called &lt;a href="https://github.com/open-hotel/open-hotel-resources/blob/master/dist/figuredata.json" rel="noopener noreferrer"&gt;figuredata.json&lt;/a&gt;. This file holds information we need to get the right image for each one of our figure parts.&lt;br&gt;
It is based on habbo's &lt;code&gt;figuredata.xml&lt;/code&gt;, but we converted it to json to make it easier to use.&lt;/p&gt;

&lt;p&gt;By using it, we can selectively lazy-load the image files as we need, since it wouldn't be practical to load everything in memory at once.&lt;/p&gt;

&lt;p&gt;It basically holds two first-level keys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;pallete&lt;/code&gt; is a dictionary that maps a &lt;code&gt;palleteid&lt;/code&gt; to a dictionary of colors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;settype&lt;/code&gt; keeps track of the &lt;code&gt;palette&lt;/code&gt;, the metadata (like gender) and the images we need to use for each figure part.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's try a step-by-step render for the &lt;code&gt;hr-679-61&lt;/code&gt; figure (hair 679 with color 61). Since it's a hair, it's held under the &lt;a href="https://github.com/open-hotel/open-hotel-resources/tree/master/dist/hh_human_hair" rel="noopener noreferrer"&gt;hh_human_hair&lt;/a&gt; &lt;code&gt;lib&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// figuredata.json&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;palette&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 3. get color "61" hex from palette "2"&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;32&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;color&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DFA66F&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;61&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;color&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2D2D2D&lt;/span&gt;&lt;span class="dl"&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;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;settype&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Access the figure type, which is "hr"&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// 2. Access the palette with id 2&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;paletteid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;set&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 4. Get hair with key 679&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;679&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// Both genders accept this hair&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gender&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;U&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parts&lt;/span&gt;&lt;span class="dl"&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;span class="c1"&gt;// 5. Since type is hr, get this part &lt;/span&gt;
              &lt;span class="c1"&gt;// id and move to figureMap.json&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;colorable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&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;// for part sets with more than one&lt;/span&gt;
              &lt;span class="c1"&gt;// color, multiple color indexes can be used&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;colorindex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&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="c1"&gt;// hrb is used when the avatar is using a hat&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hrb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;colorable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;colorindex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&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="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;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  Figure Map
&lt;/h1&gt;

&lt;p&gt;The &lt;a href="https://github.com/open-hotel/open-hotel-resources/blob/master/dist/figuremap.json" rel="noopener noreferrer"&gt;figuremap.json&lt;/a&gt; holds the libraries names mentioned at the &lt;code&gt;Naming Convention&lt;/code&gt; section.&lt;/p&gt;

&lt;p&gt;At figuremap.json, the first-level keys are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/open-hotel/open-hotel-resources/blob/master/dist/figuremap.json#L2" rel="noopener noreferrer"&gt;libs&lt;/a&gt;&lt;br&gt;
An array with all the libraries names.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/open-hotel/open-hotel-resources/blob/master/dist/figuremap.json#L1020" rel="noopener noreferrer"&gt;parts&lt;/a&gt;&lt;br&gt;
A dictionary that maps a partset name and a part id to an index of the &lt;code&gt;libs&lt;/code&gt; array.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To get the lib we need, let's follow the steps:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// figuremap.json&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;libs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="c1"&gt;// 3. Get the lib id from the 1004th position of the array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hh_human_hair&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ha&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Access the "hr" partset&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// 2. Access the part with id 27 and get its lib index&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;27&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1004&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;Now we have all the information we need to get our hair image, we just need to &lt;a href="https://github.com/open-hotel/open-hotel-client/blob/structure-migration/src/game/imager/avatar/AvatarChunk.ts#L44" rel="noopener noreferrer"&gt;build&lt;/a&gt; the image file name.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image files naming
&lt;/h3&gt;

&lt;p&gt;The resultant file name should be:&lt;br&gt;
&lt;code&gt;hh_human_hair_h_std_hr_4_2_0&lt;/code&gt;, where each part of the file indicates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;hh_human_hair&lt;/code&gt;: this file is a part of the human hair &lt;code&gt;lib&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;h&lt;/code&gt;: the image size (it could be &lt;code&gt;sh&lt;/code&gt; if it were zoomed out). At Open Hotel we're not using &lt;code&gt;sh&lt;/code&gt; images, since zoom is handled by &lt;a href="https://github.com/davidfig/pixi-viewport" rel="noopener noreferrer"&gt;pixi-viewport&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;std&lt;/code&gt;: the image's action. &lt;code&gt;std&lt;/code&gt; is the standard, but it could be &lt;code&gt;wlk&lt;/code&gt; for walking, or &lt;code&gt;sml&lt;/code&gt; for smile.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For some reason, the action &lt;code&gt;mv&lt;/code&gt; matches the files with the &lt;code&gt;wlk&lt;/code&gt; action name. This mapping happens at &lt;a href="https://github.com/open-hotel/open-hotel-resources/blob/master/dist/HabboAvatarAnimations.json" rel="noopener noreferrer"&gt;animations.json&lt;/a&gt;. I might cover animations in detail in a future article.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hr&lt;/code&gt;: the figure part, which in the case is &lt;code&gt;hair&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;4&lt;/code&gt;: the figure part id for this specific hair.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;2&lt;/code&gt;: the position, which can vary from 0 to 7 rotation clock-wise&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmiw4jgvu00urao29fadg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmiw4jgvu00urao29fadg.png" alt="positionsN"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt;: the animation frame. Actions like &lt;code&gt;std&lt;/code&gt; only have one frame (frame 0), but for animations like walking (&lt;code&gt;mv&lt;/code&gt;) and waving(&lt;code&gt;wave&lt;/code&gt;), more frames are required.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This tutorial might be confusing, but that's because a lot of mappings are required, and animating can get even more complicated.&lt;/p&gt;

&lt;p&gt;The goal here is to provide a general idea of how the rendering process works at Habbo and also encourage people to contribute to &lt;a href="https://github.com/open-hotel" rel="noopener noreferrer"&gt;Open Hotel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The current client active branch is &lt;a href="https://github.com/open-hotel/open-hotel-client/tree/structure-migration" rel="noopener noreferrer"&gt;structure-migration&lt;/a&gt;. If you run this branch, you should be able to look at the code that actually rendered the images present in this article.&lt;/p&gt;

&lt;p&gt;If you're interested in contributing or have any questions, you can contact me at &lt;a href="mailto:trickstival@gmail.com"&gt;trickstival@gmail.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks!!&lt;/p&gt;

</description>
      <category>habbo</category>
      <category>opensource</category>
      <category>games</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to classify log status on DataDog</title>
      <dc:creator>Patrick Stival</dc:creator>
      <pubDate>Mon, 13 Jan 2020 20:30:44 +0000</pubDate>
      <link>https://dev.to/trickstival/how-to-classify-log-status-on-datadog-2p86</link>
      <guid>https://dev.to/trickstival/how-to-classify-log-status-on-datadog-2p86</guid>
      <description>&lt;p&gt;DataDog is a great tool for Data Analysis and Log Management. I started using it recently, and the tools they provide really impressed me.&lt;/p&gt;

&lt;p&gt;First things first, you will need to collect your logs before monitoring them. There are many ways to do so, through preexisting &lt;a href="https://docs.datadoghq.com/integrations/" rel="noopener noreferrer"&gt;integrations&lt;/a&gt; or by collecting them from &lt;a href="https://docs.datadoghq.com/agent/logs/?tab=tailexistingfiles" rel="noopener noreferrer"&gt;files&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By default, every log message is classified as &lt;code&gt;info&lt;/code&gt;, but it can be changed to any &lt;a href="https://en.wikipedia.org/wiki/Syslog#Severity_level" rel="noopener noreferrer"&gt;Severity Level&lt;/a&gt; described by syslog.&lt;/p&gt;

&lt;p&gt;The possible log status are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Emergency&lt;/li&gt;
&lt;li&gt;Alert&lt;/li&gt;
&lt;li&gt;Critical&lt;/li&gt;
&lt;li&gt;Error&lt;/li&gt;
&lt;li&gt;Warning&lt;/li&gt;
&lt;li&gt;Notice&lt;/li&gt;
&lt;li&gt;Informational&lt;/li&gt;
&lt;li&gt;Debug&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have collected your logs, it's time to classify them.&lt;br&gt;
To do so, you have to create a pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pipeliiines
&lt;/h2&gt;

&lt;p&gt;Pipelines are workflows that hold one or more processors inside. Processors are predefined data manipulators that can be executed in line. For this post we are going to use 3 Processors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Grok Parser&lt;/li&gt;
&lt;li&gt;String Builder&lt;/li&gt;
&lt;li&gt;Status Remapper&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creating a Pipeline
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to Logs &amp;gt; Configuration on the sidebar menu&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F95dcur452hp183dnrzx6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F95dcur452hp183dnrzx6.png" alt="Configuration Sidebar Image"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on "New Pipeline" at the upper corner of the page&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fiha387wfba1mbpvv10vq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fiha387wfba1mbpvv10vq.png" alt="New Pipeline Button Image"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Name your Pipeline&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F59nbc0ygg4qijbiclyuz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F59nbc0ygg4qijbiclyuz.png" alt="Creating pipeline"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once you created the pipeline, you should be able to add processors:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fz07h0zcp0qn94420edii.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fz07h0zcp0qn94420edii.png" alt="Create processor image"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Parsing the logs
&lt;/h3&gt;

&lt;p&gt;The next step is to parse the logs. For that purpose you can use the Grok Parser and extract information from your text. You can find more information about parsing rules by clicking &lt;a href="https://docs.datadoghq.com/logs/processing/parsing/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Choose the Grok Parser as the processor.&lt;br&gt;
Then, provide some log samples (you can get those on the &lt;a href="https://app.datadoghq.com/logs" rel="noopener noreferrer"&gt;Data Dog Logs Page&lt;/a&gt;) and write your own &lt;a href="https://docs.datadoghq.com/logs/processing/parsing/?tab=matcher" rel="noopener noreferrer"&gt;Parsing Rules&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A Parsing Rule is defined by the following structure:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&amp;lt;Name of the Rule&amp;gt; Pattern %{matcher1:attribute}...

You can also use filters combined with matchers

&amp;lt;Name of the Rule&amp;gt; Pattern %{filter1} Pattern2 %{matcher1:attribute}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.datadoghq.com/logs/processing/parsing/?tab=matcher" rel="noopener noreferrer"&gt;Read more&lt;/a&gt; about matchers and filters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check out the Grok Parser configuration:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F766tmbea0420otn64hs8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F766tmbea0420otn64hs8.png" alt="Grok Parser Configuration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In most cases, just one rule should be enough. In the example there is just one rule called &lt;code&gt;ImmaRule&lt;/code&gt; and we collect the attribute &lt;code&gt;timestamp&lt;/code&gt; as a date and &lt;code&gt;username&lt;/code&gt; as a word, by using &lt;a href="https://docs.datadoghq.com/logs/processing/parsing/?tab=matcher" rel="noopener noreferrer"&gt;matchers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you extracted all the data you needed, a JSON representation of the attributes and values extracted through parsing you specified with the data samples will show up on the bottom of the screen:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fs0jo2s7aqlxvqx3szblh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fs0jo2s7aqlxvqx3szblh.png" alt="JSON Representation of the extracted data"&gt;&lt;/a&gt;.&lt;br&gt;
Name your processor and click save.&lt;/p&gt;

&lt;h3&gt;
  
  
  Classifying the logs
&lt;/h3&gt;

&lt;p&gt;The Grok Parser will keep forward only with the logs that matched the pattern you specified earlier. Since it's a pipeline, you can safely classify all logs filtered as errors, warnings, or any other status.&lt;/p&gt;

&lt;p&gt;In this case I want to classify them as errors. For this purpose, we're adding a &lt;a href="https://docs.datadoghq.com/logs/processing/processors/?tab=ui#string-builder-processor" rel="noopener noreferrer"&gt;String Builder&lt;/a&gt; processor to the pipeline beneath the Grok Parser.&lt;br&gt;
This way we can store the log level in a new attribute and officialize it as the status attribute later by using the &lt;a href="https://docs.datadoghq.com/logs/processing/processors/?tab=ui#log-status-remapper" rel="noopener noreferrer"&gt;Log Status Remapper&lt;/a&gt; processor.&lt;/p&gt;

&lt;p&gt;So I'll add the String Builder processor and add the attribute &lt;code&gt;level&lt;/code&gt; to it, with the value &lt;code&gt;error&lt;/code&gt;:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F73uyeu1ookd9aqz67r0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F73uyeu1ookd9aqz67r0w.png" alt="String builder processor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And finally set the new &lt;code&gt;level&lt;/code&gt; attribute as the status attribute, by adding the &lt;a href="https://docs.datadoghq.com/logs/processing/processors/?tab=ui#log-status-remapper" rel="noopener noreferrer"&gt;Log Status Remapper&lt;/a&gt; processor after the String Builder on the pipeline:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fddy1uk0kkg408q40yx8i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fddy1uk0kkg408q40yx8i.png" alt="Status remapper example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And finally, our pipeline is done!&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F11bzb7ya5om3yvozttij.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F11bzb7ya5om3yvozttij.png" alt="Finished Pipeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you should get your logs on the &lt;a href="https://app.datadoghq.com/logs" rel="noopener noreferrer"&gt;Log Explorer&lt;/a&gt; flagged with the &lt;code&gt;level&lt;/code&gt; you chose previously&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fa0s7opodou8w8ot1c53k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fa0s7opodou8w8ot1c53k.png" alt="Log explorer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;br&gt;
I hope this tutorial to be somehow useful for you.&lt;/p&gt;

</description>
      <category>datadog</category>
      <category>logs</category>
      <category>status</category>
      <category>level</category>
    </item>
  </channel>
</rss>
