<?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>Forem: Mickaël A</title>
    <description>The latest articles on Forem by Mickaël A (@miqwit).</description>
    <link>https://forem.com/miqwit</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%2F342386%2F909f2c28-a10f-4db4-9c30-81da4dc52908.jpg</url>
      <title>Forem: Mickaël A</title>
      <link>https://forem.com/miqwit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/miqwit"/>
    <language>en</language>
    <item>
      <title>Drawing 3D lines in Mapbox with the threebox plugin</title>
      <dc:creator>Mickaël A</dc:creator>
      <pubDate>Tue, 18 Jun 2024 05:33:22 +0000</pubDate>
      <link>https://forem.com/miqwit/drawing-3d-lines-in-mapbox-with-the-threebox-plugin-5b0</link>
      <guid>https://forem.com/miqwit/drawing-3d-lines-in-mapbox-with-the-threebox-plugin-5b0</guid>
      <description>&lt;p&gt;Recently, I encountered a use case where I needed to draw a line in 3D space over a map layout. This line represents the track of a local flight, which is far more engaging to view and explore in 3D than in 2D, as it allows for a better appreciation of the elevation changes.&lt;/p&gt;

&lt;p&gt;I used &lt;a href="https://www.mapbox.com/" rel="noopener noreferrer"&gt;Mapbox&lt;/a&gt; for my online map, given its popularity and its use in the Strava application, which I frequently use.&lt;/p&gt;

&lt;p&gt;Mapbox offers a convenient method for displaying GeoJSON data files on a map, as detailed in this &lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/geojson-line/" rel="noopener noreferrer"&gt;Mapbox documentation page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the scene with Mapbox
&lt;/h2&gt;

&lt;p&gt;I work within a PHP Symfony application and use the NPM package manager. To integrate Mapbox, I installed the &lt;a href="https://www.npmjs.com/package/mapbox-gl" rel="noopener noreferrer"&gt;mapbox-gl&lt;/a&gt; library using the command &lt;code&gt;npm i mapbox-gl&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;I also &lt;strong&gt;created a Mapbox account and token&lt;/strong&gt; (&lt;a href="https://docs.mapbox.com/help/getting-started/access-tokens/" rel="noopener noreferrer"&gt;help page here&lt;/a&gt;, &lt;a href="https://docs.mapbox.com/help/getting-started/access-tokens/" rel="noopener noreferrer"&gt;token page here&lt;/a&gt;) to use in my code.&lt;/p&gt;

&lt;p&gt;Here is how the basic map creation looks in my project:&lt;/p&gt;

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

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mapbox-gl/dist/mapbox-gl.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pk....&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// your full token here&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;map&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// the HTML element where the maps load&lt;/span&gt;
    &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mapbox://styles/mapbox/outdoors-v12&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// the Mapbox style of the map&lt;/span&gt;
    &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;6.1229882&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;43.24953278&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// starting position [lng, lat]&lt;/span&gt;
    &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&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;For a more detailed setup of your first map in your project, you can refer to this page: &lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/simple-map/" rel="noopener noreferrer"&gt;Display a map on a web page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding 3D terrain
&lt;/h2&gt;

&lt;p&gt;Since I want to enjoy my map in 3D, adding terrain visualization is essential. This feature is not enabled by default. In Mapbox terminology, this involves using a &lt;code&gt;raster-dem&lt;/code&gt; layer. You can find an example of this setup &lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/add-terrain/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In my context, the code looks like this:&lt;/p&gt;

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

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exaggeration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Add terrain source&lt;/span&gt;
&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mapbox-dem&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="s1"&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="s1"&gt;raster-dem&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="s1"&gt;url&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="s1"&gt;mapbox://mapbox.mapbox-terrain-dem-v1&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="s1"&gt;tileSize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;maxzoom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// add the DEM source as a terrain layer with exaggerated height&lt;/span&gt;
&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTerrain&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;source&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="s1"&gt;mapbox-dem&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="s1"&gt;exaggeration&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;exaggeration&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;As you can see, it's very close to the sample. Now, when I ctrl+click and move my mouse, I can see the elevation. The &lt;code&gt;exaggeration&lt;/code&gt; constant helps to dramatize the terrain a bit, allowing for better appreciation of the relief.&lt;/p&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%2Fuploads%2Farticles%2Fdo3yonhil1xr1n2o2agb.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%2Fuploads%2Farticles%2Fdo3yonhil1xr1n2o2agb.png" alt="Terrain comarison without and with raster DEM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding my GeoJSON track to the map
&lt;/h2&gt;

&lt;p&gt;As I mentioned earlier, Mapbox provides a convenient way to add GeoJSON data to my map, which is &lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/simple-map/" rel="noopener noreferrer"&gt;documented here&lt;/a&gt;. In my case, the GeoJSON data is derived from processing a GPX file on the backend. I have my GeoJSON file stored on disk.&lt;/p&gt;

&lt;p&gt;Here is how I load and display the GeoJSON data on the map:&lt;/p&gt;

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

&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;load&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="o"&gt;=&amp;gt;&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="nf"&gt;addSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;air-track&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;geojson&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;geojson&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// geojson contains the path to my local geojson file&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="nf"&gt;addLayer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;air-track-line&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;line&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;air-track&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;paint&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;line-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;#dd0000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// red&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;line-width&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;This is enough to display something like this:&lt;/p&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%2Fuploads%2Farticles%2Flvr7wpasqatzk11oxixr.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%2Fuploads%2Farticles%2Flvr7wpasqatzk11oxixr.png" alt="GeoJSON flat track in Mapbox"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Make this line 3D
&lt;/h2&gt;

&lt;p&gt;Here we are pushing beyond the capabilities of Mapbox. Unfortunately, Mapbox's 3D capabilities with GeoJSON have limitations. It's important to note that my GeoJSON data includes elevation information, as per standard. Here is a sample of my data:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nl"&gt;"geometry"&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="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MultiLineString"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"coordinates"&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="w"&gt;
        &lt;/span&gt;&lt;span class="mf"&gt;6.12827066&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;lat&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="mf"&gt;43.24983118&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;lng&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="mf"&gt;76.7122&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;elevation&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-29T09:10:14Z"&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;used)&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="mf"&gt;6.12817452&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="mf"&gt;43.24956302&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="mf"&gt;81.7742&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-29T09:10:20Z"&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="mf"&gt;6.12814958&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="mf"&gt;43.24928749&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="mf"&gt;80.3541&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-29T09:10:29Z"&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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;


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

&lt;/div&gt;

&lt;p&gt;In various discussions and &lt;a href="https://stackoverflow.com/questions/47283304/mapbox-extruding-lines" rel="noopener noreferrer"&gt;Stack Overflow issues&lt;/a&gt;, some workarounds have been suggested, but they don't fully meet my requirements.&lt;/p&gt;

&lt;p&gt;The flexibility I need can be achieved by importing a Mapbox plugin called &lt;a href="https://github.com/peterqliu/threebox" rel="noopener noreferrer"&gt;threebox&lt;/a&gt;, which is a combination of &lt;a href="https://threejs.org/" rel="noopener noreferrer"&gt;three.js&lt;/a&gt; and Mapbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding threebox to the equation
&lt;/h2&gt;

&lt;p&gt;I &lt;a href="https://www.npmjs.com/package/threebox-plugin" rel="noopener noreferrer"&gt;installed Threebox&lt;/a&gt; and connected it to my map:&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;// Creating tb related to my Mapbox map&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tb&lt;/span&gt; &lt;span class="o"&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;tb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Threebox&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="c1"&gt;// my mapbox map&lt;/span&gt;
    &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCanvas&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webgl&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="na"&gt;defaultLights&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// On load, add a custom layer&lt;/span&gt;
&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;load&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&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="nf"&gt;addLayer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;custom_layer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;custom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;render&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;gl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
            &lt;span class="nx"&gt;tb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&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;This is how it works: a new &lt;em&gt;custom layer&lt;/em&gt; is added to the scene, allowing us to draw specific shapes in the 3D space of Mapbox. The Threebox plugin is featured in this &lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/add-3d-model-threebox/" rel="noopener noreferrer"&gt;Mapbox documentation page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Initially, I was hesitant to delve into more complex tools, but after overcoming some integration challenges, using it turned out to be quite straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drawing in 3D
&lt;/h2&gt;

&lt;p&gt;To add a line to the scene, you can use the convenient &lt;a href="https://github.com/peterqliu/threebox/blob/master/docs/Threebox.md" rel="noopener noreferrer"&gt;&lt;code&gt;tb.line()&lt;/code&gt;&lt;/a&gt; function. It supports the elevation parameter out of the box.&lt;/p&gt;

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

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line_segment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;geometry&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="nx"&gt;lat1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lng1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;elevation1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lat2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lng2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;elevation2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#dd0000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;opacity&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="nx"&gt;tb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line_segment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This would create a line and add it to the scene.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drawing the GeoJSON data
&lt;/h2&gt;

&lt;p&gt;To connect Threebox with the data needed to draw, the challenge lies in retrieving GeoJSON features from the scene. While Mapbox provides a &lt;code&gt;querySourceFeatures&lt;/code&gt; method to fetch features from a source, I didn't manage to use it properly. My array of the features was empty, no matter what. I opted for a disk load, as my data is on a file accessible from this script.&lt;/p&gt;

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

&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sourcedata&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sourceId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;air-track&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isSourceLoaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;geojson&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&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;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jres&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&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;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;coords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="nf"&gt;draw3dLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;coords&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;I am listening for the &lt;code&gt;sourcedata&lt;/code&gt; event. If the event's sourceId matches &lt;code&gt;air-track&lt;/code&gt; (which is the source ID I used in &lt;code&gt;map.addSource()&lt;/code&gt; above), I then load the GeoJSON data from the file and store the coordinates. Here's an approach to achieve this:&lt;/p&gt;

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

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;draw3dLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&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="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&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;i&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="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Draw the segment in space&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line_segment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;geometry&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="nx"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&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="nx"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&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="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;exaggeration&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;exaggeration&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#dd0000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;opacity&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="nx"&gt;tb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line_segment&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;Note that I reused the &lt;code&gt;exaggeration&lt;/code&gt; constant here as I exaggerated the relief. Without it, the proportions would not be respected and the line could cross the terrain.&lt;/p&gt;

&lt;p&gt;The core logic is implemented in that function. For each pair of coordinates in the source, it draws a line in space, resulting in something like this:&lt;/p&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%2Fuploads%2Farticles%2Fcxypqnk1wnnmihc21oez.gif" 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%2Fuploads%2Farticles%2Fcxypqnk1wnnmihc21oez.gif" alt="3D track in Mapbox with threebox"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I found it a bit difficult to appreciate the track, so I added a vertical line after each 3D segment. Each of these lines has coordinates identical to the last segment point, set at elevation zero (lat, lng).&lt;/p&gt;

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

// Draw the vertical line at the end of the segment
const line_vertical = tb.line({
    geometry: [
        [coords[i - 1][0], coords[i - 1][1], coords[i - 1][2] * exaggeration],
        [coords[i - 1][0], coords[i - 1][1], coords[i - 1][0]]
    ],
    color: '#dd0000',
    width: 1,
    opacity: .5
})
tb.add(line_vertical);


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

&lt;/div&gt;

&lt;p&gt;Which draws as:&lt;/p&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%2Fuploads%2Farticles%2F4iyzv81bojvekvsixk4f.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%2Fuploads%2Farticles%2F4iyzv81bojvekvsixk4f.png" alt="Vertical lines to reflect the elevation better"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Displaying a shadow
&lt;/h1&gt;

&lt;p&gt;I retained the GeoJSON track feature provided by Mapbox to maintain the visible line on the ground. I simply reduced its width to 1 and changed the color to dark gray, creating the impression of a shadow.&lt;/p&gt;

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

map.addSource("air-track", {
    type: "geojson",
    data: geojson,
});

map.addLayer({
    id: "air-track-line",
    type: "line",
    source: "air-track",
    paint: {
        "line-color": "#111111", // dark gray
        "line-width": 1,         // thin width
    },
});


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

&lt;/div&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%2Fuploads%2Farticles%2F5zumik742xp5rhzpmkwj.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%2Fuploads%2Farticles%2F5zumik742xp5rhzpmkwj.png" alt="Full 3D track with vertical lines and a shadow on the ground"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;It took me more time to find the right tools to solve my problem than to actually code the algorithm itself. In the context of my Symfony project, it also took some time to integrate GeoJSON, Mapbox, and threebox altogether.&lt;/p&gt;

&lt;p&gt;I'm very enthusiastic about the result, and I'm sharing it here because there wasn't an obvious way to do so on the web.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A Metric for Technical Debt</title>
      <dc:creator>Mickaël A</dc:creator>
      <pubDate>Thu, 22 Sep 2022 08:55:36 +0000</pubDate>
      <link>https://forem.com/miqwit/a-metric-for-technical-debt-1g08</link>
      <guid>https://forem.com/miqwit/a-metric-for-technical-debt-1g08</guid>
      <description>&lt;p&gt;Technical debt is a prominent topic for any company running code as its main asset. However, unlike many other part of the company, there is often no clear metric about it.&lt;/p&gt;

&lt;p&gt;And there is a good reason for that: it's complicated.&lt;/p&gt;

&lt;p&gt;At Vialma, I need to measure it in order to be able to evaluate our debt, to track it, and to pay it back!&lt;/p&gt;

&lt;h2&gt;
  
  
  Main features of the metric
&lt;/h2&gt;

&lt;p&gt;The metric I came up with is compound. It is hard to list and understand everything involved in technical debt. However, some features are more obvious than others. I focused on them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Technologies versions&lt;/strong&gt;. I met a company that were working on Django 1.*. That's debt. I consider here web server versions (apache, nginx, ...), language version (Python, PHP, ...) or framework version (React, Symfony, ...). In this metric, I compare the current version of our technology with the latest version on the market, and count the number of days elapsed since the release of that version.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tests&lt;/strong&gt;. The code can be ugly... but if it's tested, at least we know it is doing what we expect out of it. A code without tests is very likely to become an important debt to the company.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dependencies&lt;/strong&gt;. The technical debt of a highly dependent code will grow very fast. It is easier and faster to code with many dependencies at first, however much harder to maintain it. I include dependencies in my metric.&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%2Fuploads%2Farticles%2Faxx9hse9v7rn1nafa0f4.jpg" 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%2Fuploads%2Farticles%2Faxx9hse9v7rn1nafa0f4.jpg" alt="Example of tech debt metric"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  About the metric value
&lt;/h2&gt;

&lt;p&gt;The metric above is 5.48. It does not mean much by itself, what is of first interest to me is how it is &lt;strong&gt;progressing&lt;/strong&gt; from one month to the other.&lt;/p&gt;

&lt;p&gt;It will naturally grow, since the elapsed days of latest technologies continuously grow. Decisions to merge projects, bootstrap new technologies, waiting too long for refactoring or avoiding tests will now have an impact on the metric. It's exactly what I wanted!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why managing technical debt is important?
&lt;/h2&gt;

&lt;p&gt;It is not always obvious to business or product owners. I like to remind the importance of technical debt control on the business this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Development time&lt;/strong&gt;: a clean, maintained code makes any new development faster, whatever the experience of the developer. This can be counter-intuitive to some developers when you think of the time you need to code and maintain tests, but it truly is!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stack attractiveness&lt;/strong&gt;: it is hard to hire PHP developers already... It is MUCH harder if you are running Symfony 3 on PHP 5...&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Maintenance time&lt;/strong&gt;: you can't safely move from version X to X+1 if your code is untested and unstable. What happens too often is that migration is never planned ("we didn't have time"!)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Technical Debt is not a bogeyman in the company any more. It is a controlled variable behind a transparent, simple metric, like any other important business matter.&lt;/p&gt;

&lt;p&gt;I would like to add more features to this metric later, like the number of developers per project, code coverage (instead of lines of test code), security issues detected by Snyk or a similar tool, or the number of errors raised in production with Sentry or Kibana for example.&lt;/p&gt;

&lt;p&gt;Maybe for a version 2 of the metric!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>8 unexpected tips for remote workers</title>
      <dc:creator>Mickaël A</dc:creator>
      <pubDate>Sat, 29 Jan 2022 08:26:10 +0000</pubDate>
      <link>https://forem.com/miqwit/8-unexpected-tips-for-remote-workers-35k8</link>
      <guid>https://forem.com/miqwit/8-unexpected-tips-for-remote-workers-35k8</guid>
      <description>&lt;p&gt;As a manager of a team of 8 developers and working in a full-remote company across the globe, I share here a list of simple yet useful tips to make remote work more efficient.&lt;/p&gt;

&lt;p&gt;👤&lt;strong&gt;Use profile pictures&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In all the tools you're using, use a picture of you as a profile picture. Do not leave a default one or do not use unrelated content. It helps the team members know each other and remembering that we are all humans after all...&lt;/p&gt;

&lt;p&gt;🖼️&lt;strong&gt;Post personal photos&lt;/strong&gt; of things you are doing out of work. Show your pets, family members, how your surroundings look like, outdoor activities... It might seem weird at first, but it is very appreciated and commented. Use appropriate channels and the right frequency (once a week?).&lt;/p&gt;

&lt;p&gt;⌨️&lt;strong&gt;Learn to type fast&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Written communication is so omnipresent in remote teams that fast-typers deal better with team work. Expressing ideas will be a lot easier if you can type fast. If it's not your case, don't despair and use train regularly with tools like &lt;a href="https://www.keybr.com/"&gt;kebr&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;🙌&lt;strong&gt;Use emojis&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They seem unprofessional at first, but they are a premium artefact to have rich remote conversation. Sometimes an emoji is just what you need to react to an assertion. Remote teams use emojis as a second, and subtle, language.&lt;/p&gt;

&lt;p&gt;📹&lt;strong&gt;Use your video&lt;/strong&gt; in all meetings, at least for the first and the last minutes. Every meeting is an opportunity to connect to others and show how you look today. We all need this to work together! People deactivating it are suddenly more distant and can alter a team spirit after a while.&lt;/p&gt;

&lt;p&gt;⏰&lt;strong&gt;Be punctual&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Respect scrupulously meeting end and start times. It is frustrating to wait for someone in front of a screen. Being on time is more respectful and more efficient than ever.&lt;/p&gt;

&lt;p&gt;📅&lt;strong&gt;Reduce the default meeting length&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Remote meetings are more energy-consuming than their physical counterpart. They can be more frustrating as well. Use 20 or 30 minutes as the default duration. 40 minutes for long meeting and 1 hour for exceptionally long meetings.&lt;/p&gt;

&lt;p&gt;🌴&lt;strong&gt;Expose your break times&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Personal and professional spheres are more porous than ever. Indicate in your calendar your availability zones and make clear when you won't be working (beginning and end of a regular day, breaks, focus times...). This will reduce your stress and liberate a lot of positive energy.&lt;/p&gt;

&lt;p&gt;Happy remote work! 🎉&lt;/p&gt;

</description>
      <category>remote</category>
      <category>tips</category>
      <category>team</category>
    </item>
    <item>
      <title>Dealing with Code Legacy</title>
      <dc:creator>Mickaël A</dc:creator>
      <pubDate>Mon, 11 Oct 2021 18:43:50 +0000</pubDate>
      <link>https://forem.com/miqwit/dealing-with-code-legacy-28m2</link>
      <guid>https://forem.com/miqwit/dealing-with-code-legacy-28m2</guid>
      <description>&lt;p&gt;Code legacy is an inevitable topic in software, that can create a lot of frustration : "We must rewrite this part because it's too poorly coded and unmaintainable, but we don't have time!"&lt;/p&gt;

&lt;p&gt;The problem is generally that the actual deciders undervalue the issue or don't see it at all. Imagine the lobby of the main building being cluttered by a pile of trash, growing every day... Would the boss allow time to clean it? &lt;/p&gt;

&lt;p&gt;Cleaning existing code is called refactoring. During refactoring, developers rearrange the code in a nice way so that it is more readable, more maintainable, and often more performant as well.&lt;/p&gt;

&lt;p&gt;Refactoring should be a common word in any software-related business or product. It's a normal activity. It is like washing your hands in a hospital or watering the plants in a flower shop. As a CTO, I request at least 20 % per week to dedicate to it. &lt;/p&gt;

&lt;p&gt;I consider 3 levels of refactoring: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;On-the-fly. The refactoring is tiny and related to another task (or card or ticket depending on the terminology). It is done while coding that task, potentially inflating the estimation of that card.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refactoring task. The refactoring has been identified in a previous work and been exposed to the management team. It is extracted as another task, estimated and planned like any other task. That fits well in the 20% quota.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refactoring projects. Sometimes the refactoring will impact many different sections of the code. It cannot be isolated as a task so it becomes a project. Like any other project in the company, it will be divided into tasks, estimated and scheduled accordingly in the 20% or more if it meets more directly product expectations.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Refactoring projects
&lt;/h2&gt;

&lt;p&gt;Refactoring projects are the riskiest because they compete with regular project in terms of time and effort. In short, they will probably need more than the 20% quota and mobilise management for a while. At first, many tasks will create no visible and cost money. That's why it is important to compartmentalized them as much as possible, reduce the scope and sensitize product and business deciders of the long-term outcomes.&lt;/p&gt;

&lt;p&gt;The situation is critical because without refactoring any new development will actually increase the technical debt: more unexpected bugs, harder-to-maintain code, more desperation, less attractiveness for the developers. It &lt;em&gt;has&lt;/em&gt; to be done.&lt;/p&gt;

&lt;p&gt;This needs tech leadership. If the important refactoring do not occur because "we don't have time", then it's a tech management issue. Sometimes developers can use the "time" argument as an excuse. It's worth digging: the refactoring could be delayed (it can wait a few more months/years), is smaller than we though (or bigger!), developers just don't have the right coding practices or are not skilled enough to maintain an old and big code stack. In all of these cases a great deal of attention is required by the tech management.&lt;/p&gt;

&lt;h2&gt;
  
  
  "The big rewrite"
&lt;/h2&gt;

&lt;p&gt;The big rewrite consists in rewriting entire part of the business' code. It sometimes sounds like the best option, especially to (junior?) developers. "If only this was coded like this since the beginning...". &lt;/p&gt;

&lt;p&gt;It's a trap!&lt;/p&gt;

&lt;p&gt;It is extremely rare that TBR is the right solution. Even "hopeless" situations can be turned into smaller projects, from basic code rearranging to re-architecturing. Then little by little the code will be refactored, without interrupting the business and losing enormous amount of time &amp;amp; money which is usually the case with TBR.&lt;/p&gt;

&lt;p&gt;That is only possible with a higher-level technical analysis, typically done by the CTO with the help of developers, a very good organization and an excellent communication: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what is included and excluded for each step&lt;/li&gt;
&lt;li&gt;what has been done after each iteration&lt;/li&gt;
&lt;li&gt;how difficult the current step is and how to restructure next steps. &lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tunnelling effect is dangerous here and must be monitored carefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code legacy is also good
&lt;/h2&gt;

&lt;p&gt;I would like to conclude on a positive note : code legacy is also a good sign. It means an organization has built something and used it for the business, most likely with limited time and resource. Identifying code debt means a progress in coding expectations and business strategy as a whole. &lt;/p&gt;

&lt;p&gt;The legacy defines who we are as an organization, what we have been through, what decisions were good or bad. One who reads the legacy will learn a lot about the business past and present.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>My experience with Dual Boot Windows/Linux</title>
      <dc:creator>Mickaël A</dc:creator>
      <pubDate>Sun, 22 Nov 2020 16:08:04 +0000</pubDate>
      <link>https://forem.com/miqwit/my-experience-with-dual-boot-windows-linux-3h5</link>
      <guid>https://forem.com/miqwit/my-experience-with-dual-boot-windows-linux-3h5</guid>
      <description>&lt;p&gt;I bought a laptop one year ago, and decided to run it with a dual boot, after using mine on Ubuntu for years.&lt;/p&gt;

&lt;h1&gt;
  
  
  Linux (still) has trouble with my hardware
&lt;/h1&gt;

&lt;p&gt;It's quite a constant with Linux distributions, and has always been a struggle in all experiences I had. In my case, my laptop is a Dell XPS with an IPS OLED Infinity HD screen. I love this screen (on Windows) but on Linux, it is sucking all my battery and its brightness cannot be adjusted from my keyboard keys.&lt;/p&gt;

&lt;p&gt;The first time I used my Linux with this screen without my energy cable plugged in, I lasted only 1 hour and was quite in trouble to continue working that day! After hours to try to fix the problem, my best option was to reduce the brightness (with a specific software, &lt;a href="https://www.debugpoint.com/2016/10/2-ways-fix-laptop-brightness-problem-ubuntu-linux/"&gt;method 2 here&lt;/a&gt;), which actually saves a lot of energy. &lt;/p&gt;

&lt;h1&gt;
  
  
  Small hiccups with Linux
&lt;/h1&gt;

&lt;p&gt;Some little annoying things still happen on my Ubuntu. It may seems nothing at first, but after a while it is quite annoying. Some of them include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Some software can't deal with that superb resolution of my screen and displays text in a very very very small font, making it unusable. Usually there is a way to make them bigger (in the settings), but it's something I never have on Windows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When my Firefox requests a restart (new version I presume), it never restart when I click on the Restart button. The OS didn't get the instruction properly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I can't pin all software in my navigation bar. Again, there is probably a way to do so, but I didn't find it yet...&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The shared links in Slack does not open in my already opened Firefox, but in a subprocess of Slack. It is annoying because then my credentials are not maintained (it's like a new private browsing), which makes it very annoying for work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At some point Slack was flickering and was not usable at all. I had to use it on a browser tab instead.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When I reboot my machine from Linux, and expects the dual boot menu to appears, it does not. I have to shut it down, and then start it from scratch (pressing the Power button). The first time I was really scared that my dual boot set up failed! &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Some software really take too much resources (RAM), like Chrome or Zoom. Which is really annoying knowing that I bought a new laptop partly to solve this issue... Now, when I have a Zoom call, I reboot on Windows.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You make think these are small, and that in most cases I "just have to do this and that". My whole point here is that I don't have to do it on Windows. Everything is working out of the box.&lt;/p&gt;

&lt;h1&gt;
  
  
  My Windows OS is very efficient
&lt;/h1&gt;

&lt;p&gt;So far I don't have much to complain about my Windows OS. I know the reasons, and the tight partnership Microsoft deals with manufacturers. In the case of my laptop and in particular the screen, it makes a huge difference in my daily experience.&lt;/p&gt;

&lt;h1&gt;
  
  
  For coding, Linux is far more better
&lt;/h1&gt;

&lt;p&gt;Every time I am entering a coding session, I reboot on Linux. Things are smoother, much more integrated, and I am much faster. On Windows, I use Git for Windows but I hate the terminal (I should try to change it...). And I didn't set up the Linux WSL thing, which seems promising in my case.&lt;/p&gt;

&lt;p&gt;In particular, Docker is far more useable on Linux than Windows. I expected that because of the core idea of the technology the experience would be the same on any OS, but not at all (so far). I think the main problem is the file system on which Docker technology heavily relies.&lt;/p&gt;

&lt;h1&gt;
  
  
  My screen is so beautiful...
&lt;/h1&gt;

&lt;p&gt;... on Windows. I see the drivers are updated frequently. When it comes to watch a long video or a film, I always use Windows, which makes my experience amazing. On Linux, it's... flickering! Videos apart, my screen is amazing on both OSes :) &lt;/p&gt;

&lt;h1&gt;
  
  
  Dual boot is the ideal solution for me
&lt;/h1&gt;

&lt;p&gt;I don't regret this choice at all, and in my case it's probably the best choice I could come up with. I did not try many Linux distributions as I was suggested to, I just stick with Ubuntu. Maybe another distribution would perfectly match my hardware (I hardly doubt it, though).&lt;/p&gt;

&lt;p&gt;Also, something important is that booting either on Windows or Linux is super quick (a bit longer from Linux as I explained above). If there is a Windows update, I can totally delay that to the next time I boot on Windows. It is not impacting my Linux booting time at all.&lt;/p&gt;

&lt;h1&gt;
  
  
  A shared partition
&lt;/h1&gt;

&lt;p&gt;I create an NTFS partition that is both r/w on Linux and Windows to share most of my personal data. It works fine and has been a good option so far. I happened that because of Windows sleep mode I was not able to write on that partition under Linux, but I could fix it disabling fast startup.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;My job is a lot of code related stuff without coding, and I manage it on Windows. Windows became my main OS because of this. However, for personal projects and integrated development session, I don't lose time with Windows and switch to Linux.&lt;/p&gt;

</description>
      <category>dualboot</category>
      <category>windows</category>
      <category>linux</category>
      <category>dell</category>
    </item>
    <item>
      <title>From programmer to CTO. A journey</title>
      <dc:creator>Mickaël A</dc:creator>
      <pubDate>Sun, 15 Nov 2020 16:15:53 +0000</pubDate>
      <link>https://forem.com/miqwit/from-developer-to-cto-a-journey-5478</link>
      <guid>https://forem.com/miqwit/from-developer-to-cto-a-journey-5478</guid>
      <description>&lt;p&gt;Not so long ago, I was a developer. I worked for various companies as an employee, and more recently as an independent programmer. In one of my contracts, I found the stack very scattered, reflecting long months and even years of tech inconsistency, yet on solid technological choices. I shared this observation with my client, and suggested him to hire a CTO. He or she would refactor the current architecture and avoid the same mistake to happen again.&lt;/p&gt;

&lt;p&gt;He asked me to jump in that role. &lt;/p&gt;

&lt;p&gt;I said "yes".&lt;/p&gt;

&lt;p&gt;My career slightly started to shift. Here are some important take-aways I wish I had known before.&lt;/p&gt;

&lt;h1&gt;
  
  
  It's hard work
&lt;/h1&gt;

&lt;p&gt;Taking the ownership of a full tech stack is tough. I means reading a lot of &lt;del&gt;technical documentation&lt;/del&gt; code, go through a lot of configuration files, git history, API calls, unused code (which I didn't know it was unused), environment creation, testing, backup-ing, playing with prod (which I didn't know was prod)... That means a lot of different technologies, in different contexts. I was happy to have 12 years of experience and being able to fluently read most of the files in there. &lt;/p&gt;

&lt;p&gt;In the meanwhile, new code has to be released, and bugs have to be fixed. It's not like the company is stopping its activity because the CTO has to understand it all.&lt;/p&gt;

&lt;p&gt;This amount of work was quite impressive at first, and scared me in some situations.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. It's interesting work
&lt;/h1&gt;

&lt;p&gt;I found very interesting to dive into entire projects. Mistakes were made, most of them understandable given the context (of a small startup) and good decisions were made as well. I learned from all of these situations. I started to be able to understand decisions not only from a technical perspective, but also from a business one. Bad technical decisions can be good business ones (and vice-versa?). In other terms, creating tech debt is not necessary a bad thing for the business, as long as the debt is known, documented, and at some point reimbursed!&lt;/p&gt;

&lt;p&gt;Another interesting part is to be close to the strategic orientation of the business. In my case, the product is very tech-centered. This creates a strong dependency between business and tech. The business will go were the tech can bring it. The strategic discussions resulting from this fact are very interesting, and I was missing them while a developer. The perspective it brings is enlightening.&lt;/p&gt;

&lt;h1&gt;
  
  
  2. It's a lot of non-tech work
&lt;/h1&gt;

&lt;p&gt;I was very frustrated at the beginning of anything that would not be related to coding. I really thought I was loosing precious time to achieve more. To a dev, meetings, HR, debates, documentation is a waste of time. For a CTO, it's the regular job. For a dev to be efficient, some people around him or her must make it possible and the CTO is one of them. Being able to estimate (roughly) and take a decision means discussions, meetings and a lot of information. I slowly accepted that and reduced dramatically the amount of code I was able to deliver, unfortunately. I don't find it a waste of time any more.&lt;/p&gt;

&lt;p&gt;The frustrating part is to be able to know and decide without understanding the full stack perfectly.&lt;/p&gt;

&lt;h1&gt;
  
  
  3. The "how long will it take" question
&lt;/h1&gt;

&lt;p&gt;This is always a difficult question to ask an engineer, because &lt;strong&gt;it depends&lt;/strong&gt; too much on too many parameters. And this is not an practical answer to any decision-maker. To answer this question means to start building a solution, or to be more accurate to architecture it. That is when business meets tech: the way to build it depends on the business orientation, and vice-versa. Still, this question is crucial to any CEO, and answering it means to me: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;knowing the current stack perfectly&lt;/li&gt;
&lt;li&gt;creating a couple of realistic architectures (sometimes in seconds during a meeting)&lt;/li&gt;
&lt;li&gt;saying: &lt;strong&gt;"it depends"&lt;/strong&gt; 😂 And detailing the one or two good options.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The CEO knows enough of the history of the stack, the people involved and the tone of my voice to take action. At early stages, "long and difficult" or "short and easy" are good enough answers. The "quick and dirty" can occasionally be a good one, although never the one I prefer...&lt;/p&gt;

&lt;h1&gt;
  
  
  4. There is a lot of HR
&lt;/h1&gt;

&lt;p&gt;When a developer or a contractor says he/she is leaving, that means to me: "I must find about 20 hours in the next months to handle this situation". Indeed, preparing the leave, looking for someone, checking the skills, onboarding, checking the skills again... takes a lot of my time. And this time is very fragmented. I find interesting to dig in profiles, meet new people and build a team, but the amount of work related to it is almost prohibitive.&lt;/p&gt;

&lt;p&gt;When people are not leaving (most of the time!) I take a little time frequently to address 1-to-1 chats. It helps create a stronger relationship, especially in a full remote team like ours. At first I looked at it as a waste of time, but I now see the value and enjoy it. I am never busy enough to skip 1-to-1s with my devs.&lt;/p&gt;

&lt;p&gt;HR includes also career consideration. Developers are not here just to execute work written on Trello cards. Their experience, personality and goals in professional life are always important. Knowing them one by one is a long process, but a necessary one. I must find the win-win situation that empowers both each individual and the company. Accurately knowing one team's skills at one time is also a precious information for business orientation.&lt;/p&gt;

&lt;p&gt;Having such responsibility of team is something I did not expect this way.&lt;/p&gt;

&lt;h1&gt;
  
  
  5. Every day is very different
&lt;/h1&gt;

&lt;p&gt;I really miss my long hours of development and building of a cool feature, or even refactoring! On the other hand, my days are very different from one to the other. Having to interact with the CEO, the Product Owner, the Project Manager, the testers and the developers during a single week is enriching. They all have skills that nurture me. It also requires very strong organizational skills.&lt;/p&gt;

&lt;p&gt;Considering the relatively small size of my team, I must perform additional tech tasks that nobody else would do. Not because they lack the skills, but because we pay them for something else. For instance, I am the main DevOps and the one who pushes to prod every week, and I am the one architecturing new product-wide features.&lt;/p&gt;

&lt;h1&gt;
  
  
  6. This is a humble journey
&lt;/h1&gt;

&lt;p&gt;I think that any of the devs in our team could do my job. I can barely do theirs now. Gathering information and talking to people did not seem like a real skill to me... It is. There are many situations where I just "don't know". I say it humbly when it happens, and I understand my job is often to "know" in a reasonable time (ASAP). This instability is very scary at first, and more than once I trembled to estimate, push to prod, or meet with an important person. Confidence is growing in me. &lt;/p&gt;

&lt;p&gt;I really enjoy the journey so far, and I really think it was a good time for me to embark. Joining a "small project getting bigger" was a relevant situation for me to become a CTO.&lt;/p&gt;

</description>
      <category>career</category>
      <category>cto</category>
      <category>management</category>
      <category>startup</category>
    </item>
    <item>
      <title>Optimizing Slack communications</title>
      <dc:creator>Mickaël A</dc:creator>
      <pubDate>Sat, 17 Oct 2020 13:17:09 +0000</pubDate>
      <link>https://forem.com/miqwit/surviving-the-slack-era-27ki</link>
      <guid>https://forem.com/miqwit/surviving-the-slack-era-27ki</guid>
      <description>&lt;p&gt;You've probably noticed it, Slack has become an omnipresent tool in the corporate work place. For people like me, on a computer for the past two decades, it's just another chatting application in the work place: useful and invasive. It's probably more useful than any app from the previous generations, though. And definitely more invasive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use threads
&lt;/h2&gt;

&lt;p&gt;Slack is beeping so often that when one comes back to read what's up, threads will help digging into conversations (and referencing them) and skip the useless ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't just ask "Hey, how are you?"
&lt;/h2&gt;

&lt;p&gt;Most professional conversations are asynchronous. It means the other person is not on Slack waiting for your message. If you want to ask something, do so in your first message (you can still say "Hey, how are you?" in the same message) and if you need an attention for a conversation just say it like: "Hey how are you? Do you have some minutes for me (on Slack) to sort something out?".&lt;/p&gt;

&lt;h2&gt;
  
  
  Never use &lt;code&gt;@here&lt;/code&gt; or &lt;code&gt;@channel&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I never found a good use of these mentions. In an actual workplace that would be standing up, call everyone out loud and gather them in the same meeting room and then ask your dramatic question: "where is the document titled &lt;em&gt;Best Practices&lt;/em&gt;?". It is very rare that emergencies actually impact everyone in a channel, and I believe true emergencies as rare as well. &lt;/p&gt;

&lt;p&gt;Just target people one by one. A channel is already a place to warn every one at once, all of them will see your message anyway, that is why they belong to the channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure your Slack
&lt;/h2&gt;

&lt;p&gt;You can read &lt;a href="https://dev.to/jess/how-i-avoid-slacking-off-on-slack-8lj"&gt;How I Configure Slack for Productivity and Sanity&lt;/a&gt; on DEV.&lt;/p&gt;

&lt;h2&gt;
  
  
  Group your messages into one
&lt;/h2&gt;

&lt;p&gt;It is annoying to receive 10 messages because you're slacking out loud. There is a cool feature that allows you to &lt;em&gt;edit&lt;/em&gt; previous messages. Rather than piling up on new information one line after the other, just edit the previous message (&lt;code&gt;up&lt;/code&gt; + &lt;code&gt;e&lt;/code&gt;) and add new lines in it. When your conversation partner will come back from the toilets he/she will see one notification, not twenty eight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't say &lt;code&gt;Thank You&lt;/code&gt; or &lt;code&gt;OK&lt;/code&gt; on old conversations. Use emojis
&lt;/h2&gt;

&lt;p&gt;Emojis is a cool feature, because it is carrying information in a fun, discreet and efficient way. If someone said something cool one hour ago, don't come back from your lunch typing "OK". It will reactivate the conversation, while the sender is on to something else, and completely forgot about this. Reading an "OK" notification will disturb him/her bringing very little information. It's good to know you've read it and approved by reacting with an emoji.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not organize meetings on Slack
&lt;/h2&gt;

&lt;p&gt;Asking for time availabilities is boring and useless. Shared calendars offer this feature. Use it! Ask me on Slack when I'm available and I will probably answer you: "Check my calendar".&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't mention people in the &lt;code&gt;#random&lt;/code&gt; channel
&lt;/h2&gt;

&lt;p&gt;This channel and similar ones are for fun, relaxed and non-important information. Do not disturb people in these channels. Mention them without the &lt;code&gt;@&lt;/code&gt; so maybe later they will see it and react to it. Or not. It's fine. It's just &lt;code&gt;#random&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use emails
&lt;/h2&gt;

&lt;p&gt;I read that the whole point of Slack is to suppress email. I disapprove. Emails are still useful especially for longer asynchronous conversations. If you expect the receiver to take time and to think before answering you, spare him/her some time from Slack and send an email. It is implicitly accepted that Slack is used for quicker interactions, and not all professional interactions need a quick answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not over-communicate
&lt;/h2&gt;

&lt;p&gt;It is true that remote work needs more communication. Nevertheless, one needs to spare others' time and do not constantly share everything on Slack, all day long (you can use Tweeter for that). Subtly, things will slow down and let your team focus on a more important thing: work.&lt;/p&gt;

&lt;p&gt;All that said, I am happy to use Slack rather than Skype, Messenger or ICQ to chat with friends or colleagues. It is an amazing product with a lot of great features. It is just too easy to let practices drifting away and spend too much time on Slack. &lt;/p&gt;

&lt;p&gt;Happy slacking!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Using PHP xdebug in a Docker with Netbeans</title>
      <dc:creator>Mickaël A</dc:creator>
      <pubDate>Sun, 04 Oct 2020 15:38:11 +0000</pubDate>
      <link>https://forem.com/miqwit/using-php-xdebug-in-a-docker-with-netbeans-3hc9</link>
      <guid>https://forem.com/miqwit/using-php-xdebug-in-a-docker-with-netbeans-3hc9</guid>
      <description>&lt;p&gt;There are quite some guides around to connect xdebug-docker to PHPStorm, or xdebug to netbeans... but not a lot regarding xdebug-docker and netbeans. After struggling I managed to find a way. Here is how.&lt;/p&gt;

&lt;h1&gt;
  
  
  Configuring docker
&lt;/h1&gt;

&lt;p&gt;The Docker image must have xdebug installed. I use the following lines to do so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM php:7.4-cli
[...]

RUN pecl channel-update pecl.php.net &amp;amp;&amp;amp; pecl install xdebug-2.8.1 \
    &amp;amp;&amp;amp; docker-php-ext-enable xdebug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Running the image
&lt;/h1&gt;

&lt;p&gt;After building you docker image (above is just an extract, you may need many other things for your project), here is how I run it. And here is where I struggled most...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run \
  --name=project_name \
  -v /home/me/dev/project/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini \
  -v /home/me/dev/project:/home/me/dev/project \
  --rm \
  --env XDEBUG_CONFIG=\"idekey=my-ide-key\" \
  --network=host \
  -ti my-docker-img bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first &lt;code&gt;-v&lt;/code&gt; option is copying a local xdebug.ini file inside the docker, at a location it will be read by PHP. From my image inherited from &lt;code&gt;FROM php:7.4-cli&lt;/code&gt;, I know the folder &lt;code&gt;/usr/local/etc/php/conf.d/xdebug.ini&lt;/code&gt; will do.&lt;/p&gt;

&lt;p&gt;This is a flexible way to play with parameters in your docker PHP config. Expect this will not work from the first time...&lt;/p&gt;

&lt;p&gt;In my case this &lt;code&gt;xdebug.ini&lt;/code&gt; file contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;xdebug.remote_enable=on
xdebug.remote_autostart=off
xdebug.remote_host=127.0.0.1
xdebug.remote_log="/tmp/xdebug.log"
xdebug.idekey="my-ide-key"
xdebug.remote_port=9123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are the required minimal configuration. I highly suggest to use a &lt;code&gt;remote_log&lt;/code&gt; parameter, because if it fails, it's where you'll have valuable data.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;idekey&lt;/code&gt; will be used in Netbeans as well as the remote port.&lt;/p&gt;

&lt;p&gt;The line &lt;code&gt;-v /home/me/dev/project:/home/me/dev/project&lt;/code&gt; is also very important. Your project file names and directories inside your container must &lt;strong&gt;be the same as your host architecture&lt;/strong&gt;. If not, Netbeans will stop but will be unable to show you the actual line.&lt;/p&gt;

&lt;p&gt;The line &lt;code&gt;--env XDEBUG_CONFIG=\"idekey=my-ide-key\"&lt;/code&gt; is kind of duplicate of the &lt;code&gt;xdebug.ini&lt;/code&gt; configuration &lt;code&gt;xdebug.idekey="my-ide-key"&lt;/code&gt;... This parameter needs (also?) to be an environment variable, so don't forget it.&lt;/p&gt;

&lt;p&gt;The line &lt;code&gt;--network=host&lt;/code&gt; is also very important. It means that the &lt;code&gt;xdebug.remote_host=127.0.0.1&lt;/code&gt; will also be the host IP. Otherwise, xdebug will be stuck inside your docker and unable to talk to the Host, where Netbeans sits.&lt;/p&gt;

&lt;p&gt;In your docker, check your xdebug parameters are properly set by using the command &lt;code&gt;php -i | grep xdebug&lt;/code&gt;. You will see such an output, reflecting your configuration (and the configuration used the next time you use the &lt;code&gt;php&lt;/code&gt; command):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
xdebug.remote_enable =&amp;gt; On =&amp;gt; On
xdebug.remote_handler =&amp;gt; dbgp =&amp;gt; dbgp
xdebug.remote_host =&amp;gt; 127.0.0.1 =&amp;gt; 127.0.0.1
xdebug.remote_log =&amp;gt; /tmp/xdebug.log =&amp;gt; /tmp/xdebug.log
xdebug.remote_log_level =&amp;gt; 7 =&amp;gt; 7
xdebug.remote_mode =&amp;gt; req =&amp;gt; req
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it looks OK, carry on, otherwise fix your config first.&lt;/p&gt;

&lt;h1&gt;
  
  
  Configuring Netbeans
&lt;/h1&gt;

&lt;p&gt;This is the easier part. Go to Tools | Options | PHP | Debugging.&lt;/p&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%2Fa7grq881c5e01y6wg5ux.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%2Fa7grq881c5e01y6wg5ux.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make these parameters match the &lt;code&gt;xdebug.ini&lt;/code&gt; config in your docker, as visible on the picture (fields &lt;code&gt;Debugger Port&lt;/code&gt; and &lt;code&gt;Session ID&lt;/code&gt;)&lt;/p&gt;

&lt;h1&gt;
  
  
  Launching your debugging session
&lt;/h1&gt;

&lt;p&gt;In Netbeans, click on the following icon (or ctrl+F5):&lt;/p&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%2Fslm0zmdz1z0jmvkm1zej.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%2Fslm0zmdz1z0jmvkm1zej.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Place a breakpoint somewhere in your application, as early as possible (or use the &lt;code&gt;Stop at first line&lt;/code&gt; option provided by Netbeans, as visible in the screenshot above).&lt;/p&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%2F4fq5vaft38k9qbn97d60.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%2F4fq5vaft38k9qbn97d60.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;breakpoint (red square) set at line 63 here&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In your docker launch a PHP command, like &lt;code&gt;php bin/phpunit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The command will stop, and Netbeans will be stop at the required line. BINGO!&lt;/p&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%2F67mulobmf12yejhr55nz.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%2F67mulobmf12yejhr55nz.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The debugger stopped here (green arrow)&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Basic debugger use
&lt;/h1&gt;

&lt;p&gt;The most useful commands to me are the following one (step into, go to next instruction etc.)&lt;/p&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%2Fmog3mjbvcqysp8duvvi7.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%2Fmog3mjbvcqysp8duvvi7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the Variables and Call Stack panels. You can display them from the Window | Debugging menu if they don't show up automatically.&lt;/p&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%2Fjqtrs6wbz1i9fm6a1rht.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%2Fjqtrs6wbz1i9fm6a1rht.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Troubleshooting
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Make sure your configuration is active
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;php -i | grep xdebug&lt;/code&gt; is a good start. If you don't run your project with the &lt;code&gt;php&lt;/code&gt; command, be sure (by displaying a &lt;code&gt;phpinfo()&lt;/code&gt; for example) that the xdebug.ini is applied, e.g. in a place which is considered by your PHP executable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the logs
&lt;/h2&gt;

&lt;p&gt;If you copied my documentation, there should be a logging file, inside your docker, at the location: &lt;code&gt;/tmp/xdebug.log&lt;/code&gt;. Read that!&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the xdebug.ini parameters match the Netbeans ones.
&lt;/h2&gt;

&lt;p&gt;I am sure you checked it 10 times but who knows... &lt;/p&gt;

</description>
      <category>docker</category>
      <category>xdebug</category>
      <category>netbeans</category>
      <category>php</category>
    </item>
  </channel>
</rss>
