<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Alessandro's Website</title>
        <link>https://trinca.tornidor.com</link>
        <description>Alessandro's Resume and blog</description>
        <lastBuildDate>Tue, 21 Apr 2026 13:12:18 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Alessandro's Website</title>
            <url>https://trinca.tornidor.com/qrcode.png</url>
            <link>https://trinca.tornidor.com</link>
        </image>
        <copyright>© 2023-present, Alessandro Trinca Tornidor</copyright>
        <item>
            <title><![CDATA[SamGIS QGIS plugin - inferenza locale con SAM2]]></title>
            <link>/it/projects/samgis-qgis-plugin</link>
            <guid isPermaLink="false">/it/projects/samgis-qgis-plugin</guid>
            <pubDate>Mon, 09 Mar 2026 10:00:00 GMT</pubDate>
            <description><![CDATA[Instance segmentation con SAM2 direttamente in QGIS, offline e in locale]]></description>
            <content:encoded><![CDATA[<h1 id="samgis-qgis-plugin-instance-segmentation-con-sam2-sul-tuo-pc" tabindex="-1">SamGIS QGIS Plugin - Instance Segmentation con SAM2 sul tuo PC <a class="header-anchor" href="#samgis-qgis-plugin-instance-segmentation-con-sam2-sul-tuo-pc" aria-label="Permalink to &quot;SamGIS QGIS Plugin - Instance Segmentation con SAM2 sul tuo PC&quot;"></a></h1>
<p>Questo plugin per QGIS porta <a href="https://ai.meta.com/sam2/" target="_blank" rel="noreferrer">Segment Anything Model 2</a> (SAM2) di Meta dentro a QGIS permettendo di fare instance segmentation direttamente sul tuo PC e completamente offline. Nessuna API cloud, nessuna GPU dedicata — i modelli girano in locale tramite <a href="https://onnxruntime.ai/" target="_blank" rel="noreferrer">ONNX Runtime</a>.</p>
<p>Il plugin fa parte dell'ecosistema <a href="/it/projects/samgis-segment-anything-applied-to-GIS.html">SamGIS</a>. Mentre la <a href="https://huggingface.co/spaces/aletrn/samgis" target="_blank" rel="noreferrer">demo web</a> si basa su un backend FastAPI usando dei tile provider remoti (es. OpenStreetMap) SamGIS per QGIS funziona interamente offline con qualsiasi immagine georeferenziata presente in un progetto QGIS.</p>
<p>I modelli vengono scaricati una sola volta da <a href="https://huggingface.co/aletrn/sam2-onnx-weights" target="_blank" rel="noreferrer">Hugging Face</a> e salvati in locale in <code>~/.samgis/models/</code>.</p>
<h2 id="screenshots" tabindex="-1">Screenshots <a class="header-anchor" href="#screenshots" aria-label="Permalink to &quot;Screenshots&quot;"></a></h2>
<table tabindex="0">
<thead>
<tr>
<th style="text-align:center">Selezione layer e prompt</th>
<th style="text-align:center">Risultato instance segmentation</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center"><div id="image-gallery-container-qgis-menu"><GalleryNoMap :galleryID="galleryNameMenu" :imagesData="imagesDataMenu" /></div></td>
<td style="text-align:center"><div id="image-gallery-container-qgis-result"><GalleryNoMap :galleryID="galleryNameResult" :imagesData="imagesDataResult" /></div></td>
</tr>
</tbody>
</table>
<h2 id="come-funziona" tabindex="-1">Come funziona <a class="header-anchor" href="#come-funziona" aria-label="Permalink to &quot;Come funziona&quot;"></a></h2>
<ol>
<li>Installare il plugin QGIS =) (le dipendenze Python vengono gestite &quot;sotto al cofano&quot; dal plugin stesso)</li>
<li>Caricare un layer raster georeferenziato in QGIS</li>
<li>Creare layer vettoriali con prompt a punti singoli (foreground/background) o rettangoli (solo foreground)</li>
<li>Selezionare dalla finestra modale SamGIS il layer raster ed il (oppure i) layer vettoriali prescelti</li>
<li>Nel caso non sia presente neppure un modello SAM2 il plugin proporrà la scelta del modello di cui fare download (es. &quot;SAM 2.1 Tiny uint8&quot; ~32 MB per iniziare rapidamente)</li>
<li>Il processo a questo punto
<ol>
<li>converte le coordinate geografiche (o proiettate, a seconda del CRS) in coordinate pixel</li>
<li>esegue la fase di riconoscimento vero e proprio usando il modello selezionato</li>
<li>restituisce la maschera di segmentazione convertita in un layer vettoriale poligonale avente come CRS &quot;EPSG:4326&quot;</li>
</ol>
</li>
</ol>
<h2 id="funzionalita-principali" tabindex="-1">Funzionalità principali <a class="header-anchor" href="#funzionalita-principali" aria-label="Permalink to &quot;Funzionalità principali&quot;"></a></h2>
<ul>
<li><strong>Completamente offline</strong> — i modelli vengono scaricati una sola volta e salvati in locale</li>
<li><strong>8 varianti di modello</strong> — 4 dimensioni (Tiny, Small, Base+, Large) x 2 precisioni (uint8, fp32)</li>
<li><strong>Prompt a punti</strong> — punti foreground (label=1) e background (label=0)</li>
<li><strong>Prompt a rettangolo</strong> — bounding box tramite layer vettoriale poligonale</li>
<li><strong>Inferenza in background</strong> — l'interfaccia resta reattiva durante l'esecuzione del modello</li>
<li><strong>Gestione multi-banda</strong> — supporta raster in scala di grigi, RGB e multi-banda (1-3 bande)</li>
<li><strong>Output EPSG:4326</strong> — i risultati vengono aggiunti come layer vettoriali poligonali</li>
<li><strong>Gestione dei modelli</strong> — download e selezione tramite una modale dedicata alla configurazione</li>
</ul>
<h2 id="risorse" tabindex="-1">Risorse <a class="header-anchor" href="#risorse" aria-label="Permalink to &quot;Risorse&quot;"></a></h2>
<ul>
<li>Codice sorgente e documentazione completa: <a href="https://github.com/trincadev/samgis-qgis-plugin#readme" target="_blank" rel="noreferrer">trincadev/samgis-qgis-plugin</a></li>
<li>Correlati: <a href="/it/projects/samgis-segment-anything-applied-to-GIS.html">demo web SamGIS</a></li>
<li>Richiede QGIS 3.40+ LTR</li>
</ul>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[SamGIS QGIS plugin - local inference with SAM2]]></title>
            <link>/projects/samgis-qgis-plugin</link>
            <guid isPermaLink="false">/projects/samgis-qgis-plugin</guid>
            <pubDate>Mon, 09 Mar 2026 10:00:00 GMT</pubDate>
            <description><![CDATA[Bring SAM2 instance segmentation directly into QGIS, fully offline and local]]></description>
            <content:encoded><![CDATA[<h1 id="samgis-qgis-plugin-sam2-instance-segmentation-on-your-pc" tabindex="-1">SamGIS QGIS Plugin - SAM2 Instance Segmentation on Your PC <a class="header-anchor" href="#samgis-qgis-plugin-sam2-instance-segmentation-on-your-pc" aria-label="Permalink to &quot;SamGIS QGIS Plugin - SAM2 Instance Segmentation on Your PC&quot;"></a></h1>
<p>This QGIS plugin brings Meta's <a href="https://ai.meta.com/sam2/" target="_blank" rel="noreferrer">Segment Anything Model 2</a> (SAM2) into QGIS, enabling instance segmentation directly on your PC and fully offline. No cloud APIs, no dedicated GPU — models run on-device via <a href="https://onnxruntime.ai/" target="_blank" rel="noreferrer">ONNX Runtime</a>.</p>
<p>The plugin is part of the <a href="/projects/samgis-segment-anything-applied-to-GIS.html">SamGIS</a> ecosystem. While the <a href="https://huggingface.co/spaces/aletrn/samgis" target="_blank" rel="noreferrer">web demo</a> relies on a FastAPI backend using remote tile providers (e.g. OpenStreetMap), SamGIS for QGIS works entirely offline with any georeferenced image in a QGIS project.</p>
<p>Models are downloaded once from <a href="https://huggingface.co/aletrn/sam2-onnx-weights" target="_blank" rel="noreferrer">Hugging Face</a> and stored locally in <code>~/.samgis/models/</code>.</p>
<h2 id="screenshots" tabindex="-1">Screenshots <a class="header-anchor" href="#screenshots" aria-label="Permalink to &quot;Screenshots&quot;"></a></h2>
<table tabindex="0">
<thead>
<tr>
<th style="text-align:center">Select layers and prompts</th>
<th style="text-align:center">Instance segmentation result</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center"><div id="image-gallery-container-qgis-menu"><GalleryNoMap :galleryID="galleryNameMenu" :imagesData="imagesDataMenu" /></div></td>
<td style="text-align:center"><div id="image-gallery-container-qgis-result"><GalleryNoMap :galleryID="galleryNameResult" :imagesData="imagesDataResult" /></div></td>
</tr>
</tbody>
</table>
<h2 id="how-it-works" tabindex="-1">How it works <a class="header-anchor" href="#how-it-works" aria-label="Permalink to &quot;How it works&quot;"></a></h2>
<ol>
<li>Install the QGIS plugin =) (the Python dependencies are handled under the hood by the plugin itself)</li>
<li>Load a georeferenced raster layer in QGIS</li>
<li>Create vector layers with point prompts (foreground/background) or rectangles (foreground only)</li>
<li>Select the raster layer and the vector layer(s) from the SamGIS modal dialog</li>
<li>If no SAM2 model is present, the plugin will prompt you to choose and download one (e.g. &quot;SAM 2.1 Tiny uint8&quot; ~32 MB for a quick start)</li>
<li>The pipeline then
<ol>
<li>converts geographic (or projected, depending on the CRS) coordinates to pixel coordinates</li>
<li>runs the actual inference using the selected model</li>
<li>returns the segmentation mask converted to a polygon vector layer with CRS &quot;EPSG:4326&quot;</li>
</ol>
</li>
</ol>
<h2 id="key-features" tabindex="-1">Key features <a class="header-anchor" href="#key-features" aria-label="Permalink to &quot;Key features&quot;"></a></h2>
<ul>
<li><strong>Fully offline</strong> — models are downloaded once and stored locally</li>
<li><strong>8 model variants</strong> — 4 sizes (Tiny, Small, Base+, Large) x 2 precisions (uint8, fp32)</li>
<li><strong>Point prompts</strong> — foreground (label=1) and background (label=0) points</li>
<li><strong>Rectangle prompts</strong> — bounding box via polygon vector layer</li>
<li><strong>Background inference</strong> — UI stays responsive during model execution</li>
<li><strong>Multi-band handling</strong> — supports grayscale, RGB, and multi-band rasters (1-3 bands)</li>
<li><strong>EPSG:4326 output</strong> — results are added as polygon vector layers</li>
<li><strong>Model management</strong> — download and selection via a dedicated settings dialog</li>
</ul>
<h2 id="resources" tabindex="-1">Resources <a class="header-anchor" href="#resources" aria-label="Permalink to &quot;Resources&quot;"></a></h2>
<ul>
<li>Source code and full documentation: <a href="https://github.com/trincadev/samgis-qgis-plugin#readme" target="_blank" rel="noreferrer">trincadev/samgis-qgis-plugin</a></li>
<li>Related: <a href="/projects/samgis-segment-anything-applied-to-GIS.html">SamGIS web demo</a></li>
<li>Requires QGIS 3.40+ LTR</li>
</ul>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Una camminata a Santa Caterina Valfurva]]></title>
            <link>/it/photo-galleries/santa-caterina-valfurva-20251208</link>
            <guid isPermaLink="false">/it/photo-galleries/santa-caterina-valfurva-20251208</guid>
            <pubDate>Wed, 10 Dec 2025 21:16:00 GMT</pubDate>
            <description><![CDATA[Una passeggiata attraverso i boschi alpini nel bellissimo paesaggio innevato fino al rifugio Forni]]></description>
            <content:encoded><![CDATA[<h1 id="santa-caterina-valfurva-8-dicembre-2025" tabindex="-1">Santa Caterina Valfurva, 8 dicembre 2025 <a class="header-anchor" href="#santa-caterina-valfurva-8-dicembre-2025" aria-label="Permalink to &quot;Santa Caterina Valfurva, 8 dicembre 2025&quot;"></a></h1>
<h2 id="una-camminata-a-santa-caterina-valfurva" tabindex="-1">Una camminata a Santa Caterina Valfurva <a class="header-anchor" href="#una-camminata-a-santa-caterina-valfurva" aria-label="Permalink to &quot;Una camminata a Santa Caterina Valfurva&quot;"></a></h2>
<div id="map-container">
    <MapWithPolyline
      :center='[46.4198375, 10.5170255]'
      location="Santa Caterina Valfurva"
      :mapName="mapName"
      :pathUrl="`/paths/${mapName}.json`"
      :photosUrl="`/photos/${mapName}.json`"
      zoom=13
    />
</div>
<h2 id="ventinove-vedute-di-santa-caterina-valfurva-e-dintorni" tabindex="-1">Ventinove vedute di Santa Caterina Valfurva e dintorni <a class="header-anchor" href="#ventinove-vedute-di-santa-caterina-valfurva-e-dintorni" aria-label="Permalink to &quot;Ventinove vedute di Santa Caterina Valfurva e dintorni&quot;"></a></h2>
<p>Foto scattate il 8 December 2025.</p>
<div id="image-gallery-container">
    <SimpleGallery
      :galleryID="mapName"
      :imagesData="imagesData"
    />
</div>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[A walk in Santa Caterina Valfurva]]></title>
            <link>/photo-galleries/santa-caterina-valfurva-20251208</link>
            <guid isPermaLink="false">/photo-galleries/santa-caterina-valfurva-20251208</guid>
            <pubDate>Wed, 10 Dec 2025 21:16:00 GMT</pubDate>
            <description><![CDATA[A walk through alpine woods over the beautiful snowy landscape up to the Forni mountain refuge]]></description>
            <content:encoded><![CDATA[<h1 id="santa-caterina-valfurva-8-december-2025" tabindex="-1">Santa Caterina Valfurva, 8 December 2025 <a class="header-anchor" href="#santa-caterina-valfurva-8-december-2025" aria-label="Permalink to &quot;Santa Caterina Valfurva, 8 December 2025&quot;"></a></h1>
<h2 id="a-walk-around-santa-caterina-valfurva" tabindex="-1">A walk around Santa Caterina Valfurva <a class="header-anchor" href="#a-walk-around-santa-caterina-valfurva" aria-label="Permalink to &quot;A walk around Santa Caterina Valfurva&quot;"></a></h2>
<div id="map-container">
    <MapWithPolyline
      :center='[46.4198375, 10.5170255]'
      location="Santa Caterina Valfurva"
      :mapName="mapName"
      :pathUrl="`/paths/${mapName}.json`"
      :photosUrl="`/photos/${mapName}.json`"
      zoom=13
    />
</div>
<h2 id="twenty-nine-views-of-santa-caterina-valfurva-and-surroundings" tabindex="-1">Twenty-nine views of Santa Caterina Valfurva and surroundings <a class="header-anchor" href="#twenty-nine-views-of-santa-caterina-valfurva-and-surroundings" aria-label="Permalink to &quot;Twenty-nine views of Santa Caterina Valfurva and surroundings&quot;"></a></h2>
<p>Photos taken on 8 December 2025.</p>
<div id="image-gallery-container">
    <SimpleGallery
      :galleryID="mapName"
      :imagesData="imagesData"
    />
</div>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[My Ghost Writer e lite.koboldai.net, una panoramica]]></title>
            <link>/it/projects/my-ghost-writer-and-lite.koboldai.net</link>
            <guid isPermaLink="false">/it/projects/my-ghost-writer-and-lite.koboldai.net</guid>
            <pubDate>Tue, 01 Jul 2025 23:07:14 GMT</pubDate>
            <description><![CDATA[My Ghost Writer, un mio progetto open source per l'identificazione di parole duplicate in testi generati da LLM, integrato in lite.koboldai.net]]></description>
            <content:encoded><![CDATA[<h1 id="integrazione-di-my-ghost-writer-con-lite-koboldai-net-un-analisi-tecnica-approfondita" tabindex="-1">Integrazione di My Ghost Writer con lite.koboldai.net, Un'Analisi Tecnica Approfondita <a class="header-anchor" href="#integrazione-di-my-ghost-writer-con-lite-koboldai-net-un-analisi-tecnica-approfondita" aria-label="Permalink to &quot;Integrazione di My Ghost Writer con lite.koboldai.net, Un'Analisi Tecnica Approfondita&quot;"></a></h1>
<p>Tempo fa ho iniziato a scrivere la bozza di un testo.  Un po' per curiosità professionale, un po' per semplice noia, ho deciso di pensare a quale tipo di applicazione dell'intelligenza artificiale fosse fattibile, a parte l'ovvi generazione di testo tramite un prompt ad un LLM.</p>
<p>In particolare ho notato specialmente i <a href="https://en.wikipedia.org/wiki/Large_language_model" target="_blank" rel="noreferrer">LLM (Large Language Models)</a> &quot;piccoli&quot; abbiano la tendenza a ripetersi ed a inserire parole duplicate. Per questo motivo ho cercato un progetto open source che potessi eseguire sul mio pc e tramite cui individuare parole duplicate: non ho trovato niente di utile o che comunque facesse quel che volevo io.</p>
<p>Questo ha portato alla creazione di <strong><a href="https://github.com/trincadev/my_ghost_writer" target="_blank" rel="noreferrer">My Ghost Writer</a></strong>, un progetto open source che sta ora sto integrando in <strong>lite.koboldai.net</strong> — un'interfaccia web scritta in JS ed HTML senza dipendenze per <a href="https://github.com/LostRuins/koboldcpp" target="_blank" rel="noreferrer">KoboldCpp</a>.</p>
<hr>
<h2 id="lite-koboldai-net" tabindex="-1"><a href="https://github.com/lostruins/lite.koboldai.net/" target="_blank" rel="noreferrer">lite.koboldai.net</a> <a class="header-anchor" href="#lite-koboldai-net" aria-label="Permalink to &quot;[lite.koboldai.net](https://github.com/lostruins/lite.koboldai.net/)&quot;"></a></h2>
<p><a href="https://github.com/lostruins/lite.koboldai.net/" target="_blank" rel="noreferrer">lite.koboldai.net</a> è un'interfaccia web senza dipendenze progettata per l'uso come backend per <a href="https://www.cloudflare.com/learning/ai/what-is-large-language-model/" target="_blank" rel="noreferrer">modelli linguistici di grandi dimensioni (LLM)</a> come KoboldCpp.
Funziona interamente nel browser (non richiede installazione) ed è confezionata come un singolo file HTML statico:</p>
<ul>
<li>Modalità multiple: Modalità Storia, Modalità Chat, Modalità Istruttoria e Modalità Avventura per diversi tipi di interazione con l'IA.</li>
<li>Ampia compatibilità: Funziona con KoboldAI Client, KoboldCpp e AI Horde; supporta sia modelli locali che remoti.</li>
<li>Strumenti creativi: Include un editor di testo, la generazione di immagini tramite Stable Diffusion e il supporto per le schede dei personaggi e gli scenari.</li>
<li>Facile da usare: Facile da usare, stili dell'interfaccia utente personalizzabili e funzioni come il salvataggio automatico, il text-to-speech e le opzioni di ripetizione/modifica.</li>
</ul>
<p>È una buona opzione se si desidera un'interfaccia leggera e flessibile per la narrazione, il gioco di ruolo o la scrittura assistita dall'intelligenza artificiale.
La struttura del codice è un po' disordinata:</p>
<ul>
<li>Index.html monolitico con oltre 26000 righe di codice js, css e html.</li>
<li>Solo JS, nessun dattiloscritto ovviamente.</li>
<li>Il codice JS incorporato di terze parti è obsoleto.</li>
<li>Mancano test E2E.</li>
</ul>
<h2 id="il-problema-con-wordsearch-in-lite-koboldai-net" tabindex="-1">Il Problema con WordSearch in lite.koboldai.net <a class="header-anchor" href="#il-problema-con-wordsearch-in-lite-koboldai-net" aria-label="Permalink to &quot;Il Problema con WordSearch in lite.koboldai.net&quot;"></a></h2>
<p><code>WordSearch</code> (basata sulla mia <a href="https://github.com/LostRuins/lite.koboldai.net/pull/115" target="_blank" rel="noreferrer">prima implementazione</a>) in lite.koboldai.net fa semplicemente una ricerca testuale per rilevare duplicati avendo però limitazioni significative:</p>
<ul>
<li>Identifica anche parti di testo non rilevanti (es. la singola lettera &quot;a&quot;, anche dove presente dentro ad altre parole).</li>
<li>Non distingue tra parole semanticamente diverse (es. &quot;the&quot; e &quot;they&quot;).</li>
</ul>
<hr>
<h2 id="la-soluzione-stemming-nlp-con-my-ghost-writer" tabindex="-1">La Soluzione: Stemming NLP con My Ghost Writer <a class="header-anchor" href="#la-soluzione-stemming-nlp-con-my-ghost-writer" aria-label="Permalink to &quot;La Soluzione: Stemming NLP con My Ghost Writer&quot;"></a></h2>
<p>Per risolvere questo problema, ho reimplementato la logica di rilevazione dei duplicati utilizzando lo <strong>stemming NLP</strong> (tramite l'algoritmo <a href="https://tartarus.org/martin/PorterStemmer/" target="_blank" rel="noreferrer">Porter Stemming</a>, già incluso dentro a lite.koboldai.net), che riduce le parole alla loro <strong>forma radice</strong> (es. &quot;running&quot; → &quot;run&quot;). Questo approccio:</p>
<ul>
<li>Raggruppa <strong>parole semanticamente correlate</strong> (es. &quot;run&quot;, &quot;running&quot;, &quot;ran&quot;).</li>
<li>Riduce i falsi positivi concentrandosi su <strong>veri duplicati</strong>.</li>
<li>Supporta sia l'<strong>input manuale</strong> che l'<strong>upload di file</strong> per flessibilità.</li>
</ul>
<hr>
<h2 id="funzionalita-attuali-e-limitazioni" tabindex="-1">Funzionalità Attuali e Limitazioni <a class="header-anchor" href="#funzionalita-attuali-e-limitazioni" aria-label="Permalink to &quot;Funzionalità Attuali e Limitazioni&quot;"></a></h2>
<h3 id="funzionalita-principali" tabindex="-1">Funzionalità Principali <a class="header-anchor" href="#funzionalita-principali" aria-label="Permalink to &quot;Funzionalità Principali&quot;"></a></h3>
<ul>
<li><strong>Ricerca delle parole duplicate</strong>, tramite stemming.</li>
<li><strong>Thesaurus (work in progress)</strong>:
<ul>
<li>Alimentato da chiamate ad <a href="https://www.wordsapi.com/" target="_blank" rel="noreferrer">WordsAPI</a>.</li>
<li>Persistenza dei dati opzionale con un database <strong>MongoDB</strong> locale.</li>
<li>Limitato a termini comuni ⚠️, non supporta (per ora) nomi propri o espressioni con parole multiple.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="tecnologie-utilizzate" tabindex="-1">Tecnologie Utilizzate <a class="header-anchor" href="#tecnologie-utilizzate" aria-label="Permalink to &quot;Tecnologie Utilizzate&quot;"></a></h2>
<ul>
<li><strong>Backend</strong>:
<ul>
<li><strong>Python 3.10+</strong> con <strong>FastAPI</strong> per eseguire l'applicazione web.</li>
<li><strong>Structlog</strong> per il logging e la gestione degli errori.</li>
<li><strong>Poetry</strong> per la gestione delle dipendenze.</li>
<li><strong>Docker</strong> per la containerizzazione.</li>
</ul>
</li>
<li><strong>Frontend</strong>:
<ul>
<li><strong>JavaScript vanilla</strong> (nessun framework a causa dell'integrazione con lite.koboldai.net).</li>
<li><strong>Playwright</strong> per i test end-to-end (E2E).</li>
</ul>
</li>
</ul>
<h2 id="risorse" tabindex="-1">Risorse <a class="header-anchor" href="#risorse" aria-label="Permalink to &quot;Risorse&quot;"></a></h2>
<ul>
<li><a href="https://github.com/trincadev/my_ghost_writer" target="_blank" rel="noreferrer">Repository GitHub di My Ghost Writer</a></li>
<li><a href="https://github.com/lostruins/lite.koboldai.net" target="_blank" rel="noreferrer">lite.koboldai.net</a></li>
<li><a href="https://www.wordsapi.com/" target="_blank" rel="noreferrer">WordsAPI</a></li>
</ul>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[My Ghost Writer and lite.koboldai.net, an overview]]></title>
            <link>/projects/my-ghost-writer-and-lite.koboldai.net</link>
            <guid isPermaLink="false">/projects/my-ghost-writer-and-lite.koboldai.net</guid>
            <pubDate>Tue, 01 Jul 2025 23:07:14 GMT</pubDate>
            <description><![CDATA[My Ghost Writer, an open-source project for detecting duplicate words in LLM-generated text, now integrated with lite.koboldai.net]]></description>
            <content:encoded><![CDATA[<h1 id="my-ghost-writer-and-lite-koboldai-net-an-overview" tabindex="-1">My Ghost Writer and lite.koboldai.net, an overview <a class="header-anchor" href="#my-ghost-writer-and-lite-koboldai-net-an-overview" aria-label="Permalink to &quot;My Ghost Writer and lite.koboldai.net, an overview&quot;"></a></h1>
<p>Some time ago I started drafting a text. Out of professional curiosity and sheer boredom, I wondered what kind of AI applications were feasible beyond the obvious text generation via prompts to LLMs.</p>
<p>In particular, I noticed that smaller <a href="https://en.wikipedia.org/wiki/Large_language_model" target="_blank" rel="noreferrer">LLM (Large Language Models)</a> tend to repeat themselves and insert duplicate words. This led me to search for an open-source project I could run on my PC to identify duplicate words – but I found nothing useful or that did exactly what I wanted.</p>
<p>This ultimately led to the creation of <strong><a href="https://github.com/trincadev/my_ghost_writer" target="_blank" rel="noreferrer">My Ghost Writer</a></strong>, an open-source project now being integrated into <strong>lite.koboldai.net</strong> – a lightweight, dependency-free web interface for <a href="https://github.com/LostRuins/koboldcpp" target="_blank" rel="noreferrer">KoboldCpp</a>.</p>
<h2 id="lite-koboldai-net" tabindex="-1"><a href="https://github.com/lostruins/lite.koboldai.net/" target="_blank" rel="noreferrer">lite.koboldai.net</a> <a class="header-anchor" href="#lite-koboldai-net" aria-label="Permalink to &quot;[lite.koboldai.net](https://github.com/lostruins/lite.koboldai.net/)&quot;"></a></h2>
<p><a href="https://github.com/lostruins/lite.koboldai.net/" target="_blank" rel="noreferrer">lite.koboldai.net</a> is a dependency-free web interface designed as a backend for <a href="https://www.cloudflare.com/learning/ai/what-is-large-language-model/" target="_blank" rel="noreferrer">large language models (LLM)</a> like KoboldCpp.
It runs entirely in the browser (no installation required) and is packaged as a single static HTML file:</p>
<ul>
<li><strong>Multiple modes</strong>: Story mode, Chat mode, Instruction mode, and Adventure mode for different types of AI interaction.</li>
<li><strong>Broad compatibility</strong>: Works with KoboldAI Client, KoboldCpp, and AI Horde; supports both local and remote models.</li>
<li><strong>Creative tools</strong>: Includes a text editor, image generation via Stable Diffusion, and support for character sheets and scenarios.</li>
<li><strong>User-friendly</strong>: Easy to use, customizable UI styles, and features like auto-save, text-to-speech, and repeat/edit options.</li>
</ul>
<p>It's a great option if you want a lightweight, flexible interface for storytelling, role-playing, or AI-assisted writing.
However, the code structure is somewhat messy:</p>
<ul>
<li>A monolithic <code>index.html</code> with over 26,000 lines of JS, CSS, and HTML.</li>
<li>Only vanilla JS, no TypeScript obviously.</li>
<li>Outdated third-party JS code.</li>
<li>Missing E2E tests.</li>
</ul>
<h2 id="the-problem-with-wordsearch-in-lite-koboldai-net" tabindex="-1">The Problem with WordSearch in lite.koboldai.net <a class="header-anchor" href="#the-problem-with-wordsearch-in-lite-koboldai-net" aria-label="Permalink to &quot;The Problem with WordSearch in lite.koboldai.net&quot;"></a></h2>
<p>The initial version of <code>WordSearch</code> (based on my <a href="https://github.com/LostRuins/lite.koboldai.net/pull/115" target="_blank" rel="noreferrer">first implementation</a>) in lite.koboldai.net used simple text search to detect duplicates, but had significant limitations:</p>
<ul>
<li>Identified irrelevant text fragments (e.g., the single letter &quot;a&quot; even when embedded in other words).</li>
<li>Couldn't distinguish between semantically different words (e.g., &quot;the&quot; vs. &quot;they&quot;).</li>
</ul>
<h2 id="the-solution-nlp-stemming-with-my-ghost-writer" tabindex="-1">The Solution: NLP Stemming with My Ghost Writer <a class="header-anchor" href="#the-solution-nlp-stemming-with-my-ghost-writer" aria-label="Permalink to &quot;The Solution: NLP Stemming with My Ghost Writer&quot;"></a></h2>
<p>To solve this, I reimplemented the duplicate detection logic using <strong>NLP stemming</strong> (via the <a href="https://tartarus.org/martin/PorterStemmer/" target="_blank" rel="noreferrer">Porter Stemming</a> algorithm, already included in lite.koboldai.net), which reduces words to their <strong>root form</strong> (e.g., &quot;running&quot; → &quot;run&quot;). This approach:</p>
<ul>
<li>Groups <strong>semantically related words</strong> (e.g., &quot;run&quot;, &quot;running&quot;, &quot;ran&quot;).</li>
<li>Reduces false positives by focusing on <strong>real duplicates</strong>.</li>
<li>Supports both <strong>manual input</strong> and <strong>file upload</strong> for flexibility.</li>
</ul>
<h2 id="current-features-and-limitations" tabindex="-1">Current Features and Limitations <a class="header-anchor" href="#current-features-and-limitations" aria-label="Permalink to &quot;Current Features and Limitations&quot;"></a></h2>
<h3 id="main-features" tabindex="-1">Main Features <a class="header-anchor" href="#main-features" aria-label="Permalink to &quot;Main Features&quot;"></a></h3>
<ul>
<li><strong>Duplicate word detection</strong> via stemming.</li>
<li><strong>Thesaurus (work in progress)</strong>:
<ul>
<li>Powered by calls to <a href="https://www.wordsapi.com/" target="_blank" rel="noreferrer">WordsAPI</a>.</li>
<li>Optional data persistence with a local <strong>MongoDB</strong> database.</li>
<li>Limited to common terms ⚠️, doesn't support (for now) proper nouns or multi-word expressions.</li>
</ul>
</li>
</ul>
<h2 id="technologies-used" tabindex="-1">Technologies Used <a class="header-anchor" href="#technologies-used" aria-label="Permalink to &quot;Technologies Used&quot;"></a></h2>
<ul>
<li><strong>Backend</strong>:
<ul>
<li><strong>Python 3.10+</strong> with <strong>FastAPI</strong> to run the webapp.</li>
<li><strong>Structlog</strong> for logging and error handling.</li>
<li><strong>Poetry</strong> for dependency management.</li>
<li><strong>Docker</strong> for containerization.</li>
</ul>
</li>
<li><strong>Frontend</strong>:
<ul>
<li><strong>Vanilla JavaScript</strong> (no framework due to integration with lite.koboldai.net).</li>
<li><strong>Playwright</strong> for end-to-end (E2E) testing.</li>
</ul>
</li>
</ul>
<h2 id="resources" tabindex="-1">Resources <a class="header-anchor" href="#resources" aria-label="Permalink to &quot;Resources&quot;"></a></h2>
<ul>
<li><a href="https://github.com/trincadev/my_ghost_writer" target="_blank" rel="noreferrer">GitHub Repository for My Ghost Writer</a></li>
<li><a href="https://github.com/lostruins/lite.koboldai.net" target="_blank" rel="noreferrer">lite.koboldai.net</a></li>
<li><a href="https://www.wordsapi.com/" target="_blank" rel="noreferrer">WordsAPI</a></li>
</ul>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[SSH connection from WSL2 to VirtualBox VM]]></title>
            <link>/blog/from-wsl-to-virtualbox-vm</link>
            <guid isPermaLink="false">/blog/from-wsl-to-virtualbox-vm</guid>
            <pubDate>Wed, 23 Apr 2025 18:30:00 GMT</pubDate>
            <description><![CDATA[Brief notes about how to make a ssh connection from a WSL2 to a VirtualBox virtual machine]]></description>
            <content:encoded><![CDATA[<h1 id="from-wsl2-to-virtualbox-vm" tabindex="-1">From WSL2 to VirtualBox VM <a class="header-anchor" href="#from-wsl2-to-virtualbox-vm" aria-label="Permalink to &quot;From WSL2 to VirtualBox VM&quot;"></a></h1>
<p>Right now (April 2025) Microsoft permits to configure <a href="https://learn.microsoft.com/it-it/windows/wsl/" target="_blank" rel="noreferrer">WSL2</a> in <a href="https://learn.microsoft.com/en-us/windows/wsl/networking#mirrored-mode-networking" target="_blank" rel="noreferrer">mirrored networking</a> (and <a href="https://learn.microsoft.com/en-us/windows/wsl/networking#dns-tunneling" target="_blank" rel="noreferrer">DNS Tunnelling</a>) mode:</p>
<div class="language-ini vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ini</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">[wsl2]</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">networkingMode</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=mirrored</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">dnsTunneling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=true</span></span></code></pre>
</div><p>That's useful for access a webserver you run within your WSL2 instance in localhost from the host machine. E.g. a simple python webserver executed like this from the command line:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">python</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -m</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> http.webserver</span></span></code></pre>
</div><p>it's accesible from a browser on the host machine at the address <code>http://localhost:8000/</code>.</p>
<p>But... What if you you are developing a webserver within your WSL2 instance and you needs to test this webserver within a VirtualBox VM?</p>
<p>A simple solution is:</p>
<ol>
<li>
<p>check for the existance of a dedicated <a href="https://www.virtualbox.org/manual/ch06.html#network_hostonly" target="_blank" rel="noreferrer">Host-Only Ethernet Adapter</a> created by VirtualBox; its DHCP Server will use a IP/netmask and DNS configuration like this:</p>
<table tabindex="0">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>IPv4 Address</td>
<td>192.168.99.1</td>
</tr>
<tr>
<td>IPv4 Network Mask</td>
<td>255.255.255.0</td>
</tr>
</tbody>
</table>
<table tabindex="0">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>Server Address</td>
<td>192.168.99.50</td>
</tr>
<tr>
<td>Server Mask</td>
<td>255.255.255.0</td>
</tr>
<tr>
<td>Lower Address Bound</td>
<td>192.168.99.99</td>
</tr>
<tr>
<td>Upper Address Bound</td>
<td>192.168.99.254</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>Install the <a href="https://techcommunity.microsoft.com/blog/educatordeveloperblog/step-by-step-enabling-hyper-v-for-use-on-windows-11/3745905" target="_blank" rel="noreferrer">Hyper-V Service, command line tool and GUI tools</a>. Then following <a href="https://learn.microsoft.com/en-us/windows-server/virtualization/hyper-v/get-started/create-a-virtual-switch-for-hyper-v-virtual-machines?tabs=powershell&amp;pivots=windows" target="_blank" rel="noreferrer">this guide</a> we can create an external switch that use our existing VirtualBox Host-Only Ethernet Adapter.</p>
</li>
<li>
<p>that's the result with ipconfig from a powershell session:</p>
<div class="language-cmd vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">cmd</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Ethernet adapter vEthernet (hyperv</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-to-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">virtualkbox):</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Connection</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">specific DNS Suffix  . :</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Link</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">local IPv6 Address . . . . . : fe80::aa:bb:cc:aaf</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">%</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">28</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">IPv4 Address. . . . . . . . . . . : </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">192.168.99.1</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Subnet Mask . . . . . . . . . . . : </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">255.255.255.0</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">Default</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Gateway . . . . . . . . . :</span></span></code></pre>
</div></li>
<li>
<p>In the end it's possible the connect from the WSL2 instance to the VirtualBox VM like this:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ssh</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> user@192.168.99.51</span></span></code></pre>
</div></li>
</ol>
<p>Where <code>192.168.99.51</code> is the VM ip. See also <a href="/it/blog/notes-about-ssh-port-forwarding.html">this article about ssh forwarding</a>.</p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Connessione SSH da WSL2 a VirtualBox VM]]></title>
            <link>/it/blog/from-wsl-to-virtualbox-vm</link>
            <guid isPermaLink="false">/it/blog/from-wsl-to-virtualbox-vm</guid>
            <pubDate>Wed, 23 Apr 2025 18:30:00 GMT</pubDate>
            <description><![CDATA[Brevi note su come effettuare una connessione ssh da una instanza WSL2 a una macchina virtuale VirtualBox]]></description>
            <content:encoded><![CDATA[<h1 id="da-wsl2-a-virtualbox-vm" tabindex="-1">Da WSL2 a VirtualBox VM <a class="header-anchor" href="#da-wsl2-a-virtualbox-vm" aria-label="Permalink to &quot;Da WSL2 a VirtualBox VM&quot;"></a></h1>
<p>In questo momento (aprile 2025) Microsoft permette di configurare <a href="https://learn.microsoft.com/it-it/windows/wsl/" target="_blank" rel="noreferrer">WSL2</a> in modalità <a href="https://learn.microsoft.com/en-us/windows/wsl/networking#mirrored-mode-networking" target="_blank" rel="noreferrer">mirrored networking</a> (e <a href="https://learn.microsoft.com/en-us/windows/wsl/networking#dns-tunneling" target="_blank" rel="noreferrer">DNS Tunnelling</a>):</p>
<div class="language-ini vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ini</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">[wsl2]</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">networkingMode</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=mirrored</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">dnsTunneling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=true</span></span></code></pre>
</div><p>Questo è utile per esporre un webserver eseguito all'interno della propria istanza WSL2 in localhost direttamente da un browser sulla macchina host. Ad esempio un semplice webserver python eseguito in questo modo dalla linea di comando:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">python</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -m</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> http.webserver</span></span></code></pre>
</div><p>è accessibile da un browser sulla macchina host all'indirizzo <code>http://localhost:8000/</code>.</p>
<p>Ma... Come fare nel caso si stia sviluppando un webserver all'interno della propria istanza WSL2 e si abbia bisogno di testare questo webserver all'interno di una macchina virtuale VirtualBox?</p>
<p>Una soluzione semplice è:</p>
<ol>
<li>
<p>verificare l'esistenza di un <a href="https://www.virtualbox.org/manual/ch06.html#network_hostonly" target="_blank" rel="noreferrer">Host-Only Ethernet Adapter</a> presente in VirtualBox; questo avrà configurazione IP/netmask e server DHCP fatto così:</p>
<table tabindex="0">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>IPv4 Address</td>
<td>192.168.99.1</td>
</tr>
<tr>
<td>IPv4 Network Mask</td>
<td>255.255.255.0</td>
</tr>
</tbody>
</table>
<table tabindex="0">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>Server Address</td>
<td>192.168.99.50</td>
</tr>
<tr>
<td>Server Mask</td>
<td>255.255.255.0</td>
</tr>
<tr>
<td>Lower Address Bound</td>
<td>192.168.99.99</td>
</tr>
<tr>
<td>Upper Address Bound</td>
<td>192.168.99.254</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>Installare il <a href="https://techcommunity.microsoft.com/blog/educatordeveloperblog/step-by-step-enabling-hyper-v-for-use-on-windows-11/3745905" target="_blank" rel="noreferrer">servizio Hyper-V, il relativo strumento a riga di comando e della GUI</a>. Usando poi <a href="https://learn.microsoft.com/en-us/windows-server/virtualization/hyper-v/get-started/create-a-virtual-switch-for-hyper-v-virtual-machines?tabs=powershell&amp;pivots=windows" target="_blank" rel="noreferrer">questa guida</a> creiamo uno switch esterno basato sul dispositivo di rete Host-Only VirtualBox.</p>
</li>
<li>
<p>Ecco il risultato con ipconfig da una sessione powershell:</p>
<div class="language-cmd vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">cmd</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Adattatore Ethernet vEthernet (hyperv</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-to-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">virtualkbox):</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Suffisso DNS specifico della connessione . :</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Indirizzo IPv6 Link</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">local . . . . : fe80::aa:bb:cc:aaf</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">%</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">28</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Indirizzo IPv4. . . . . . . . . . . : </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">192.168.99.1</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Maschera di sottorete . . . . . . . . . . . : </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">255.255.255.0</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Gateway predefinito. . . . . . . . . . . :</span></span></code></pre>
</div></li>
<li>
<p>Alla fine è possibile connettersi dall'istanza WSL2 alla VM VirtualBox in questo modo:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ssh</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> user@192.168.99.51</span></span></code></pre>
</div></li>
</ol>
<p>Dove <code>192.168.99.51</code> è l'ip della VM. Si veda anche <a href="/blog/notes-about-ssh-port-forwarding.html">questo articolo con cui fare port forwarding ssh</a>.</p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Notes about SSH port forwarding]]></title>
            <link>/blog/notes-about-ssh-port-forwarding</link>
            <guid isPermaLink="false">/blog/notes-about-ssh-port-forwarding</guid>
            <pubDate>Tue, 08 Apr 2025 22:58:00 GMT</pubDate>
            <description><![CDATA[notes about SSH port forwarding (local and remotes)]]></description>
            <content:encoded><![CDATA[<h1 id="notes-about-ssh-port-forwarding" tabindex="-1">Notes about SSH port forwarding <a class="header-anchor" href="#notes-about-ssh-port-forwarding" aria-label="Permalink to &quot;Notes about SSH port forwarding&quot;"></a></h1>
<h2 id="local-forwarding" tabindex="-1"><a href="https://github.com/mohammedAcheddad/SSH_port_forwarding?tab=readme-ov-file#local-forwarding" target="_blank" rel="noreferrer">Local Forwarding</a> <a class="header-anchor" href="#local-forwarding" aria-label="Permalink to &quot;[Local Forwarding](https://github.com/mohammedAcheddad/SSH_port_forwarding?tab=readme-ov-file#local-forwarding)&quot;"></a></h2>
<p>Local forwarding is used to forward a specific port on the local machine to a specific port on the remote server.
This can be useful when a service on the remote server is accessible from the local machine only through a <a href="https://it.wikipedia.org/wiki/Bastion_host" target="_blank" rel="noreferrer">Bastion Host</a> because of a firewall:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ssh</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -L</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> local-port:remote-private-service.example.com:remote-private-port</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> bastion-host-user@bastion-host.example.com</span></span></code></pre>
</div><p>For example, to access via local port 8080 to the remote port service 80 you can use the command:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ssh</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -L</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 8080:remote-private-service.example.com:80</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> bastion-host-user@bastion-host.example.com</span></span></code></pre>
</div><h2 id="remote-forwarding" tabindex="-1"><a href="https://github.com/mohammedAcheddad/SSH_port_forwarding?tab=readme-ov-file#remote-forwarding" target="_blank" rel="noreferrer">Remote Forwarding</a> <a class="header-anchor" href="#remote-forwarding" aria-label="Permalink to &quot;[Remote Forwarding](https://github.com/mohammedAcheddad/SSH_port_forwarding?tab=readme-ov-file#remote-forwarding)&quot;"></a></h2>
<p>Remote forwarding is used to forward a specific port on the remote server to a specific port on the local machine.
This can be useful when a service on the local machine is not accessible from the remote server due to a firewall. The command to use:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ssh</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -L</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> remote-private-port:local-server:local-port</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> user@remote-host.example.com</span></span></code></pre>
</div><p>For example, remote users could access a service running on a local server this way:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ssh</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -R</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 80:localhost:8080</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> user@remote-host.example.com</span></span></code></pre>
</div><h2 id="some-more-useful-ssh-options" tabindex="-1">Some more useful <a href="https://man7.org/linux/man-pages/man1/ssh.1.html" target="_blank" rel="noreferrer">SSH options</a> <a class="header-anchor" href="#some-more-useful-ssh-options" aria-label="Permalink to &quot;Some more useful [SSH options](https://man7.org/linux/man-pages/man1/ssh.1.html)&quot;"></a></h2>
<ul>
<li>
<p><code>-f</code> Requests <code>ssh</code> to go to background just before command execution. This is useful if <code>ssh</code> is going to ask for passwords or passphrases, but the user wants it in the background.<br>
This implies <code>-n</code>. [...] If the ExitOnForwardFailure configuration option is set to &quot;yes&quot;, then a client started with -f will wait for all remote port forwards to be successfully established before placing itself in the background. Refer to the description of <code>ForkAfterAuthentication</code> in <a href="https://man7.org/linux/man-pages/man5/ssh_config.5.html" target="_blank" rel="noreferrer">ssh_config(5)</a> for details.</p>
</li>
<li>
<p><code>-N</code> Do not execute a remote command. This is useful for just forwarding ports. Refer to the description of <code>SessionType</code> in <a href="https://man7.org/linux/man-pages/man5/ssh_config.5.html" target="_blank" rel="noreferrer">ssh_config(5)</a> for details.</p>
</li>
</ul>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Notes about SSH port forwarding]]></title>
            <link>/it/blog/notes-about-ssh-port-forwarding</link>
            <guid isPermaLink="false">/it/blog/notes-about-ssh-port-forwarding</guid>
            <pubDate>Tue, 08 Apr 2025 22:58:00 GMT</pubDate>
            <description><![CDATA[notes about SSH port forwarding (local and remotes)]]></description>
            <content:encoded><![CDATA[<h1 id="note-sul-forwarding-di-porte-tramite-ssh" tabindex="-1">Note sul forwarding di porte tramite SSH <a class="header-anchor" href="#note-sul-forwarding-di-porte-tramite-ssh" aria-label="Permalink to &quot;Note sul forwarding di porte tramite SSH&quot;"></a></h1>
<h2 id="local-forwarding" tabindex="-1"><a href="https://github.com/mohammedAcheddad/SSH_port_forwarding?tab=readme-ov-file#local-forwarding" target="_blank" rel="noreferrer">Local Forwarding</a> <a class="header-anchor" href="#local-forwarding" aria-label="Permalink to &quot;[Local Forwarding](https://github.com/mohammedAcheddad/SSH_port_forwarding?tab=readme-ov-file#local-forwarding)&quot;"></a></h2>
<p>Il &quot;forward locale&quot; è utilizzato per fare forward di una data porta esistente in locale ad una data porta esistente su un server remoto.
Ciò può essere utile quando un servizio sul server remoto è accessibile dal computer locale solo attraverso un <a href="https://it.wikipedia.org/wiki/Bastion_host" target="_blank" rel="noreferrer">Bastion Host</a> a causa di un firewall:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ssh</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -L</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> local-port:remote-private-service.example.com:remote-private-port</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> bastion-host-user@bastion-host.example.com</span></span></code></pre>
</div><p>Esempio, per accedere tramite la porta locale 8080 al servizio remoto esposto alla porta 80 si può utilizzare la seguente comando:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ssh</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -L</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 8080:remote-private-service.example.com:80</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> bastion-host-user@bastion-host.example.com</span></span></code></pre>
</div><h2 id="remote-forwarding" tabindex="-1"><a href="https://github.com/mohammedAcheddad/SSH_port_forwarding?tab=readme-ov-file#remote-forwarding" target="_blank" rel="noreferrer">Remote Forwarding</a> <a class="header-anchor" href="#remote-forwarding" aria-label="Permalink to &quot;[Remote Forwarding](https://github.com/mohammedAcheddad/SSH_port_forwarding?tab=readme-ov-file#remote-forwarding)&quot;"></a></h2>
<p>Il &quot;forward remoto&quot; è utilizzato per fare il forward di una data porta presente sul server remoto ad una data porta presente su una macchina locale.
Ciò può essere utile quando un servizio presente su una macchina locale non è accessibile dal server remoto a causa di un firewall. La riga di comando da usare:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ssh</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -L</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> remote-private-port:local-server:local-port</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> user@remote-host.example.com</span></span></code></pre>
</div><p>Ad esempio, degli utenti remoti possono accedere al servizio in esecuzione sul server locale in questo modo:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ssh</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -R</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 80:localhost:8080</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> user@remote-host.example.com</span></span></code></pre>
</div><h2 id="alcune-opzioni-ssh-utili" tabindex="-1">Alcune <a href="https://man7.org/linux/man-pages/man1/ssh.1.html" target="_blank" rel="noreferrer">opzioni SSH</a> utili <a class="header-anchor" href="#alcune-opzioni-ssh-utili" aria-label="Permalink to &quot;Alcune [opzioni SSH](https://man7.org/linux/man-pages/man1/ssh.1.html) utili&quot;"></a></h2>
<ul>
<li>
<p>L'opzione <code>-f</code> configura <code>ssh</code> ad andare in background appena prima dell'esecuzione del comando. Ciò è utile se <code>ssh</code> dovrà ricevere password, ma l'utente ne richiede l'esecuzione in background. Questo implica <code>-n</code>. [...]    Se la configurazione <code>ExitOnForwardFailure</code> è impostata a &quot;yes&quot;, allora un client eseguito con <code>-f</code> attende che tutte le connessioni alle porte remote siano stabilite prima di andare in background. Si faccia riferimento alla descrizione della funzione <code>ForkAfterAuthentication</code> in <a href="https://man7.org/linux/man-pages/man5/ssh_config.5.html" target="_blank" rel="noreferrer">ssh_config(5)</a>  per ulteriori dettagli.</p>
</li>
<li>
<p><code>-N</code> Non eseguire un comando remoto. Ciò è utile per eseguire solo il forwarding di porte. Si riferisce alla descrizione di <code>SessionType</code> in <a href="https://man7.org/linux/man-pages/man5/ssh_config.5.html" target="_blank" rel="noreferrer">ssh_config(5)</a> per ulteriori dettagli.</p>
</li>
</ul>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[AI Pronunciation Trainer]]></title>
            <link>/blog/ai-pronunciation-trainer</link>
            <guid isPermaLink="false">/blog/ai-pronunciation-trainer</guid>
            <pubDate>Sun, 24 Nov 2024 18:05:00 GMT</pubDate>
            <description><![CDATA[How to self-improve your speech pronunciation in a foreign language]]></description>
            <content:encoded><![CDATA[<h1 id="ai-pronunciation-trainer" tabindex="-1">AI Pronunciation Trainer <a class="header-anchor" href="#ai-pronunciation-trainer" aria-label="Permalink to &quot;AI Pronunciation Trainer&quot;"></a></h1>
<p>In this article, I present the project I am working on: <a href="https://github.com/trincadev/ai-pronunciation-trainer" target="_blank" rel="noreferrer">AI Pronunciation Trainer</a> (online <a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">here</a>), a tool designed to help you improve your pronunciation using the power of artificial intelligence. This project is a refactor of the original <a href="https://github.com/Thiagohgl/ai-pronunciation-trainer" target="_blank" rel="noreferrer">AI Pronunciation Trainer</a> by <a href="https://github.com/Thiagohgl" target="_blank" rel="noreferrer">Thiagohgl</a> to which I have made several improvements to make the tool more effective and easier to use.</p>
<h2 id="what-it-is-and-what-it-does" tabindex="-1">What it is and what it does <a class="header-anchor" href="#what-it-is-and-what-it-does" aria-label="Permalink to &quot;What it is and what it does&quot;"></a></h2>
<p><a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">AI Pronunciation Trainer</a> is a tool that uses AI to evaluate your pronunciation and provide feedback, helping you to improve and be understood more clearly. It leverages the <a href="https://github.com/snakers4/silero-models" target="_blank" rel="noreferrer">Silero STT / TTS</a>, <a href="https://openai.com/index/whisper/" target="_blank" rel="noreferrer">openai whisper</a> and <a href="https://github.com/SYSTRAN/faster-whisper" target="_blank" rel="noreferrer">faster whisper</a> models for speech-to-text functionalities (Silero does also text-to-speech), ensuring accurate and reliable pronunciation assessment.</p>
<h3 id="refactor-upgraded-frontend-and-backend-libraries" tabindex="-1">Refactor: upgraded frontend and backend libraries <a class="header-anchor" href="#refactor-upgraded-frontend-and-backend-libraries" aria-label="Permalink to &quot;Refactor: upgraded frontend and backend libraries&quot;"></a></h3>
<p>About the backend:</p>
<ul>
<li>Updated <a href="https://pytorch.org/" target="_blank" rel="noreferrer">PyTorch</a> at version 2.6.x</li>
<li>Updated Silero German Speech-to-Text model to resolve a bug that prevented the use of PyTorch versions later than 1.13.x</li>
<li>Improved backend tests with the <a href="https://en.wikipedia.org/wiki/Mutation_testing" target="_blank" rel="noreferrer">mutation test suite</a> <a href="https://cosmic-ray.readthedocs.io" target="_blank" rel="noreferrer">Cosmic Ray</a></li>
<li>Fixed a <a href="https://github.com/Thiagohgl/ai-pronunciation-trainer/issues/14" target="_blank" rel="noreferrer">bug</a> with <a href="https://huggingface.co/docs/transformers/model_doc/whisper" target="_blank" rel="noreferrer">whisper</a> not properly transcribing the end timestamp for the last word in the recorded audio (in the end I solved it switching to the <a href="https://pypi.org/project/openai-whisper/" target="_blank" rel="noreferrer">openai whisper python pip package</a>)</li>
<li>Added <a href="https://pypi.org/project/faster-whisper/" target="_blank" rel="noreferrer">faster whisper</a> model support:
<ul>
<li>it avoids <code>None</code> values on <code>end_ts</code> timestamps for the last elements, unlike the HuggingFace Whisper's output</li>
<li>it uses <a href="https://github.com/snakers4/silero-vad" target="_blank" rel="noreferrer">silero-vad</a> to detect long silences within the audio</li>
</ul>
</li>
</ul>
<p>Furthermore, regarding the frontend:</p>
<ul>
<li>Updated the JavaScript libraries using the latest versions of jQuery (3.7.1) and Bootstrap (5.3.3)</li>
<li>New frontend based on <a href="https://gradio.app" target="_blank" rel="noreferrer">Gradio</a> 5.x</li>
<li>Added E2E tests with <a href="https://playwright.dev" target="_blank" rel="noreferrer">Playwright</a></li>
<li>Added the ability to insert custom sentences to read and evaluate</li>
<li>Onboarding tour for new users made with <a href="https://github.com/kamranahmedse/driver.js/" target="_blank" rel="noreferrer">driver.js</a> and <a href="https://www.gradio.app/guides/custom-CSS-and-JS" target="_blank" rel="noreferrer">custom css/javascript in Gradio blocks</a></li>
<li>Playback of individual words in the recording followed by the 'ideal' pronunciation of the same word read by the Text-to-Speech engine</li>
<li>Also added an in-browser Text-to-Speech functionality (on Windows 11, it only works if the English and German language packs are installed)</li>
<li>Custom webApp frontend - improved CSS style on mobile devices</li>
</ul>
<h2 id="online-version-the-huggingface-space-demo" tabindex="-1">Online version: the HuggingFace Space Demo <a class="header-anchor" href="#online-version-the-huggingface-space-demo" aria-label="Permalink to &quot;Online version: the HuggingFace Space Demo&quot;"></a></h2>
<p>You can try it online using the <a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">HuggingFace Space</a>. This online demo allows you to experience the tool's capabilities without any installation or configuration. The HuggingFace Space provides a convenient and accessible way to test the AI Pronunciation Trainer and see how it can help you improve your pronunciation. Please be patient, sometimes it is a bit slow or in sleeping mode (locally it is much faster, especially if you have a powerful computer). Here is an <a href="https://aletrn-ai-pronunciation-trainer.hf.space" target="_blank" rel="noreferrer">embedded version of my HuggingFace Space</a>:</p>
<iframe
 src="https://aletrn-ai-pronunciation-trainer.hf.space"
 frameborder="0"
 allow="microphone"
 referrerpolicy="strict-origin-when-cross-origin"
 style="transform: scale(0.8) translate(-10%); width: 100%; height: 120vh;"
></iframe>
<h2 id="future-work" tabindex="-1">Future Work <a class="header-anchor" href="#future-work" aria-label="Permalink to &quot;Future Work&quot;"></a></h2>
<p>Although this tool works pretty good, there are still some areas for improvement. Here are some of the future enhancements I plan to implement:</p>
<ul>
<li>Receive feedback from the original project author (<a href="https://github.com/Thiagohgl" target="_blank" rel="noreferrer">Thiago Lobato</a>) on my documentation and changes</li>
<li>Ask the original author for explanations on the architectural and functional choices he made</li>
<li>Explore transitioning <a href="https://pytorch.org/" target="_blank" rel="noreferrer">PyTorch</a> to <a href="https://onnxruntime.ai/" target="_blank" rel="noreferrer">onnxruntime</a> (if possible)</li>
<li>Re-add the docker container (if possible)</li>
</ul>
<h2 id="conclusion" tabindex="-1">Conclusion <a class="header-anchor" href="#conclusion" aria-label="Permalink to &quot;Conclusion&quot;"></a></h2>
<p><a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">AI Pronunciation Trainer</a> is a valuable tool for anyone looking to improve their pronunciation. With the power of AI and the improvements made in the refactoring project, this tool provides accurate and reliable feedback to help you speak more clearly and confidently. I invite you to try the <a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">HuggingFace Space demo</a> and understand how this little project can help you on your journey to better pronunciation.</p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[AI Pronunciation Trainer]]></title>
            <link>/it/blog/ai-pronunciation-trainer</link>
            <guid isPermaLink="false">/it/blog/ai-pronunciation-trainer</guid>
            <pubDate>Sun, 24 Nov 2024 18:05:00 GMT</pubDate>
            <description><![CDATA[Come migliorare la vostra pronuncia di una lingua straniera]]></description>
            <content:encoded><![CDATA[<h1 id="ai-pronunciation-trainer" tabindex="-1">AI Pronunciation Trainer <a class="header-anchor" href="#ai-pronunciation-trainer" aria-label="Permalink to &quot;AI Pronunciation Trainer&quot;"></a></h1>
<p>In questo articolo presento progetto a cui sto lavorando attualmente: <a href="https://github.com/trincadev/ai-pronunciation-trainer" target="_blank" rel="noreferrer">AI Pronunciation Trainer</a> (online <a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">qui</a>), uno strumento progettato per aiutarvi a migliorare la vostra pronuncia utilizzando la potenza dell'intelligenza artificiale. Questo progetto è un refactor dell'originale <a href="https://github.com/Thiagohgl/ai-pronunciation-trainer" target="_blank" rel="noreferrer">AI Pronunciation Trainer</a> di <a href="https://github.com/Thiagohgl" target="_blank" rel="noreferrer">Thiagohgl</a> a cui ho fatto diversi miglioramenti per rendere lo strumento più efficace e facile da usare.</p>
<h2 id="cos-e-e-cosa-fa" tabindex="-1">Cos'è e cosa fa <a class="header-anchor" href="#cos-e-e-cosa-fa" aria-label="Permalink to &quot;Cos'è e cosa fa&quot;"></a></h2>
<p><a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">AI Pronunciation Trainer</a> è uno strumento che utilizza l'intelligenza artificiale per valutare la vostra pronuncia e fornire feedback, aiutandovi a migliorare e a essere compresi più chiaramente. Utilizza i modelli <a href="https://github.com/snakers4/silero-models" target="_blank" rel="noreferrer">Silero STT / TTS</a>, <a href="https://openai.com/index/whisper/" target="_blank" rel="noreferrer">openai whisper</a> e <a href="https://github.com/SYSTRAN/faster-whisper" target="_blank" rel="noreferrer">faster whisper</a> per le funzionalità di speech-to-text (Silero permette anche di fare text-to-speech), garantendo una valutazione della pronuncia accurata e affidabile.</p>
<h3 id="refactor-aggiornamento-delle-librerie-frontend-e-backend" tabindex="-1">Refactor: aggiornamento delle Librerie Frontend e Backend <a class="header-anchor" href="#refactor-aggiornamento-delle-librerie-frontend-e-backend" aria-label="Permalink to &quot;Refactor: aggiornamento delle Librerie Frontend e Backend&quot;"></a></h3>
<p>A proposito del backend:</p>
<ul>
<li><a href="https://pytorch.org/" target="_blank" rel="noreferrer">PyTorch</a> è adesso alla versione 2.6.x</li>
<li>aggiornato il modello Silero tedesco di Speech-to-Text per risolvere un bug che impediva l'utilizzo di PyTorch successivo alla versione 1.13.x.</li>
<li>Migliorati i test di backend python usando la <a href="https://en.wikipedia.org/wiki/Mutation_testing" target="_blank" rel="noreferrer">mutation test suite</a> <a href="https://cosmic-ray.readthedocs.io" target="_blank" rel="noreferrer">Cosmic Ray</a></li>
<li>Risolto un <a href="https://github.com/Thiagohgl/ai-pronunciation-trainer/issues/14" target="_blank" rel="noreferrer">bug</a> per cui <a href="https://huggingface.co/docs/transformers/model_doc/whisper" target="_blank" rel="noreferrer">whisper</a> non leggeva correttamente il timestamp finale for l'ultima parola nella registrazione (alla fine ho risolto usando il <a href="https://pypi.org/project/openai-whisper/" target="_blank" rel="noreferrer">pacchetto pip openai whisper</a>)</li>
<li>Aggiunto supporto per il <a href="https://pypi.org/project/faster-whisper/" target="_blank" rel="noreferrer">pacchetto pip faster whisper</a>:
<ul>
<li>evita i valori <code>None</code> sui <code>end_ts</code> timestamp nell'ultima parola della registrazione al contrario dell'output dell'output di whisper creato con la pipeline HuggingFace</li>
<li>permette di individuare momenti di silenzio prolungato tramite <a href="https://github.com/snakers4/silero-vad" target="_blank" rel="noreferrer">silero-vad</a></li>
</ul>
</li>
</ul>
<p>Inoltre, per quanto riguarda il frontend:</p>
<ul>
<li>Aggiornate le librerie javascript utilizzando le versioni più recenti di jQuery (3.7.1) e Bootstrap (5.3.3)</li>
<li>Nuovo frontend basato su <a href="https://gradio.app" target="_blank" rel="noreferrer">Gradio</a> 5.x</li>
<li>Aggiunti test E2E con <a href="https://playwright.dev" target="_blank" rel="noreferrer">Playwright</a></li>
<li>Aggiunta la possibilità di scrivere, leggere ed ovviamente valutare una frase a scelta libera</li>
<li>Tour guidato per i nuovi utenti con <a href="https://github.com/kamranahmedse/driver.js/" target="_blank" rel="noreferrer">driver.js</a> ed <a href="https://www.gradio.app/guides/custom-CSS-and-JS" target="_blank" rel="noreferrer">css/javascript custom dentro ai Gradio blocks</a></li>
<li>Riproduzione delle singole parole nella registrazione seguite dalla pronuncia 'ideale' della stessa parola letta dal motore Text-to-Speech</li>
<li>Aggiunto anche una funzionalità di Text-to-Speech in-browser (su Windows 11 funziona solo nel caso siano installati i pacchetti linguistici inglesi e tedesco)</li>
<li>Frontend custom webApp - migliorato lo stile CSS su dispositivi mobile</li>
</ul>
<h3 id="versione-online-la-demo-su-huggingface" tabindex="-1">Versione online: la demo su HuggingFace <a class="header-anchor" href="#versione-online-la-demo-su-huggingface" aria-label="Permalink to &quot;Versione online: la demo su HuggingFace&quot;"></a></h3>
<p>Potete provare online il mio progetto sul mio <a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">HuggingFace Space</a>. Questa demo online vi permette di sperimentare le capacità dello strumento senza alcuna installazione o configurazione. Lo spazio HuggingFace fornisce un modo conveniente e accessibile per testare AI Pronunciation Trainer e vedere come può aiutarvi a migliorare la vostra pronuncia. Si prega di essere pazienti, a volte è un po' lento oppure in sleeping nel caso non sia utilizzato da nessuno da un po' (localmente è molto più veloce, soprattutto se avete un computer potente). Ecco una <a href="https://aletrn-ai-pronunciation-trainer.hf.space" target="_blank" rel="noreferrer">versione embedded dello spazio HuggingFace</a>:</p>
<iframe
	src="https://aletrn-ai-pronunciation-trainer.hf.space"
	frameborder="0"
	allow="microphone"
	referrerpolicy="strict-origin-when-cross-origin"
	style="transform: scale(0.9) translate(-10%); width: 100%; height: 120vh;"
></iframe>
<h2 id="sviluppi-futuri" tabindex="-1">Sviluppi Futuri <a class="header-anchor" href="#sviluppi-futuri" aria-label="Permalink to &quot;Sviluppi Futuri&quot;"></a></h2>
<p>Pur funzionando piuttosto bene, ci sono ovviamente margini di miglioramento. Ecco alcuni dei miglioramenti futuri che intendo implementare:</p>
<ul>
<li>Ricevere feedback dall'autore del progetto originale (<a href="https://github.com/Thiagohgl" target="_blank" rel="noreferrer">Thiago Lobato</a>) sulla mia documentazione e sulle modifiche</li>
<li>Chiedere all'autore del lavoro originale alcune spiegazioni sulle scelte architetturali e funzionali che ha fatto</li>
<li>Valutare il passaggio da <a href="https://pytorch.org/" target="_blank" rel="noreferrer">PyTorch</a> a <a href="https://onnxruntime.ai/" target="_blank" rel="noreferrer">onnxruntime</a> (se possibile)</li>
<li>Aggiungere nuovamente il container docker (se possibile)</li>
</ul>
<h2 id="conclusione" tabindex="-1">Conclusione <a class="header-anchor" href="#conclusione" aria-label="Permalink to &quot;Conclusione&quot;"></a></h2>
<p><a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">AI Pronunciation Trainer</a> è uno strumento utile per chiunque desideri migliorare in autonomia la propria pronuncia. Con la potenza dell'IA ed i miglioramenti apportati durante il refactor, questo strumento fornisce feedback accurati e affidabili per aiutarvi a parlare in modo più chiaro e sicuro. Vi invito a provare la <a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">demo HuggingFace Space</a> e capire come questo progetto possa aiutarvi nel vostro percorso verso una migliore pronuncia.</p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[AI Pronunciation Trainer]]></title>
            <link>/it/projects/ai-pronunciation-trainer</link>
            <guid isPermaLink="false">/it/projects/ai-pronunciation-trainer</guid>
            <pubDate>Sun, 24 Nov 2024 18:05:00 GMT</pubDate>
            <description><![CDATA[Come migliorare la vostra pronuncia di una lingua straniera]]></description>
            <content:encoded><![CDATA[<h1 id="ai-pronunciation-trainer" tabindex="-1">AI Pronunciation Trainer <a class="header-anchor" href="#ai-pronunciation-trainer" aria-label="Permalink to &quot;AI Pronunciation Trainer&quot;"></a></h1>
<p>In questo articolo presento progetto a cui sto lavorando attualmente: <a href="https://github.com/trincadev/ai-pronunciation-trainer" target="_blank" rel="noreferrer">AI Pronunciation Trainer</a> (online <a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">qui</a>), uno strumento progettato per aiutarvi a migliorare la vostra pronuncia utilizzando la potenza dell'intelligenza artificiale. Questo progetto è un refactor dell'originale <a href="https://github.com/Thiagohgl/ai-pronunciation-trainer" target="_blank" rel="noreferrer">AI Pronunciation Trainer</a> di <a href="https://github.com/Thiagohgl" target="_blank" rel="noreferrer">Thiagohgl</a> a cui ho fatto diversi miglioramenti per rendere lo strumento più efficace e facile da usare.</p>
<h2 id="cos-e-e-cosa-fa" tabindex="-1">Cos'è e cosa fa <a class="header-anchor" href="#cos-e-e-cosa-fa" aria-label="Permalink to &quot;Cos'è e cosa fa&quot;"></a></h2>
<p><a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">AI Pronunciation Trainer</a> è uno strumento che utilizza l'intelligenza artificiale per valutare la vostra pronuncia e fornire feedback, aiutandovi a migliorare e a essere compresi più chiaramente. Utilizza i modelli <a href="https://github.com/snakers4/silero-models" target="_blank" rel="noreferrer">Silero STT / TTS</a>, <a href="https://openai.com/index/whisper/" target="_blank" rel="noreferrer">openai whisper</a> e <a href="https://github.com/SYSTRAN/faster-whisper" target="_blank" rel="noreferrer">faster whisper</a> per le funzionalità di speech-to-text (Silero permette anche di fare text-to-speech), garantendo una valutazione della pronuncia accurata e affidabile.</p>
<h3 id="refactor-aggiornamento-delle-librerie-frontend-e-backend" tabindex="-1">Refactor: aggiornamento delle Librerie Frontend e Backend <a class="header-anchor" href="#refactor-aggiornamento-delle-librerie-frontend-e-backend" aria-label="Permalink to &quot;Refactor: aggiornamento delle Librerie Frontend e Backend&quot;"></a></h3>
<p>A proposito del backend:</p>
<ul>
<li><a href="https://pytorch.org/" target="_blank" rel="noreferrer">PyTorch</a> è adesso alla versione 2.6.x</li>
<li>aggiornato il modello Silero tedesco di Speech-to-Text per risolvere un bug che impediva l'utilizzo di PyTorch successivo alla versione 1.13.x.</li>
<li>Migliorati i test di backend python usando la <a href="https://en.wikipedia.org/wiki/Mutation_testing" target="_blank" rel="noreferrer">mutation test suite</a> <a href="https://cosmic-ray.readthedocs.io" target="_blank" rel="noreferrer">Cosmic Ray</a></li>
<li>Risolto un <a href="https://github.com/Thiagohgl/ai-pronunciation-trainer/issues/14" target="_blank" rel="noreferrer">bug</a> per cui <a href="https://huggingface.co/docs/transformers/model_doc/whisper" target="_blank" rel="noreferrer">whisper</a> non leggeva correttamente il timestamp finale for l'ultima parola nella registrazione (alla fine ho risolto usando il <a href="https://pypi.org/project/openai-whisper/" target="_blank" rel="noreferrer">pacchetto pip openai whisper</a>)</li>
<li>Aggiunto supporto per il <a href="https://pypi.org/project/faster-whisper/" target="_blank" rel="noreferrer">pacchetto pip faster whisper</a>:
<ul>
<li>evita i valori <code>None</code> sui <code>end_ts</code> timestamp nell'ultima parola della registrazione al contrario dell'output dell'output di whisper creato con la pipeline HuggingFace</li>
<li>permette di individuare momenti di silenzio prolungato tramite <a href="https://github.com/snakers4/silero-vad" target="_blank" rel="noreferrer">silero-vad</a></li>
</ul>
</li>
</ul>
<p>Inoltre, per quanto riguarda il frontend:</p>
<ul>
<li>Aggiornate le librerie javascript utilizzando le versioni più recenti di jQuery (3.7.1) e Bootstrap (5.3.3)</li>
<li>Nuovo frontend basato su <a href="https://gradio.app" target="_blank" rel="noreferrer">Gradio</a> 5.x</li>
<li>Aggiunti test E2E con <a href="https://playwright.dev" target="_blank" rel="noreferrer">Playwright</a></li>
<li>Aggiunta la possibilità di scrivere, leggere ed ovviamente valutare una frase a scelta libera</li>
<li>Tour guidato per i nuovi utenti con <a href="https://github.com/kamranahmedse/driver.js/" target="_blank" rel="noreferrer">driver.js</a> ed <a href="https://www.gradio.app/guides/custom-CSS-and-JS" target="_blank" rel="noreferrer">css/javascript custom dentro ai Gradio blocks</a></li>
<li>Riproduzione delle singole parole nella registrazione seguite dalla pronuncia 'ideale' della stessa parola letta dal motore Text-to-Speech</li>
<li>Aggiunto anche una funzionalità di Text-to-Speech in-browser (su Windows 11 funziona solo nel caso siano installati i pacchetti linguistici inglesi e tedesco)</li>
<li>Frontend custom webApp - migliorato lo stile CSS su dispositivi mobile</li>
</ul>
<h3 id="versione-online-la-demo-su-huggingface" tabindex="-1">Versione online: la demo su HuggingFace <a class="header-anchor" href="#versione-online-la-demo-su-huggingface" aria-label="Permalink to &quot;Versione online: la demo su HuggingFace&quot;"></a></h3>
<p>Potete provare online il mio progetto sul mio <a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">HuggingFace Space</a>. Questa demo online vi permette di sperimentare le capacità dello strumento senza alcuna installazione o configurazione. Lo spazio HuggingFace fornisce un modo conveniente e accessibile per testare AI Pronunciation Trainer e vedere come può aiutarvi a migliorare la vostra pronuncia. Si prega di essere pazienti, a volte è un po' lento oppure in sleeping nel caso non sia utilizzato da nessuno da un po' (localmente è molto più veloce, soprattutto se avete un computer potente). Ecco una <a href="https://aletrn-ai-pronunciation-trainer.hf.space" target="_blank" rel="noreferrer">versione embedded dello spazio HuggingFace</a>:</p>
<iframe
	src="https://aletrn-ai-pronunciation-trainer.hf.space"
	frameborder="0"
	allow="microphone"
	referrerpolicy="strict-origin-when-cross-origin"
	style="transform: scale(0.9) translate(-10%); width: 100%; height: 120vh;"
></iframe>
<h2 id="sviluppi-futuri" tabindex="-1">Sviluppi Futuri <a class="header-anchor" href="#sviluppi-futuri" aria-label="Permalink to &quot;Sviluppi Futuri&quot;"></a></h2>
<p>Pur funzionando piuttosto bene, ci sono ovviamente margini di miglioramento. Ecco alcuni dei miglioramenti futuri che intendo implementare:</p>
<ul>
<li>Ricevere feedback dall'autore del progetto originale (<a href="https://github.com/Thiagohgl" target="_blank" rel="noreferrer">Thiago Lobato</a>) sulla mia documentazione e sulle modifiche</li>
<li>Chiedere all'autore del lavoro originale alcune spiegazioni sulle scelte architetturali e funzionali che ha fatto</li>
<li>Valutare il passaggio da <a href="https://pytorch.org/" target="_blank" rel="noreferrer">PyTorch</a> a <a href="https://onnxruntime.ai/" target="_blank" rel="noreferrer">onnxruntime</a> (se possibile)</li>
<li>Aggiungere nuovamente il container docker (se possibile)</li>
</ul>
<h2 id="conclusione" tabindex="-1">Conclusione <a class="header-anchor" href="#conclusione" aria-label="Permalink to &quot;Conclusione&quot;"></a></h2>
<p><a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">AI Pronunciation Trainer</a> è uno strumento utile per chiunque desideri migliorare in autonomia la propria pronuncia. Con la potenza dell'IA ed i miglioramenti apportati durante il refactor, questo strumento fornisce feedback accurati e affidabili per aiutarvi a parlare in modo più chiaro e sicuro. Vi invito a provare la <a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">demo HuggingFace Space</a> e capire come questo progetto possa aiutarvi nel vostro percorso verso una migliore pronuncia.</p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[AI Pronunciation Trainer]]></title>
            <link>/projects/ai-pronunciation-trainer</link>
            <guid isPermaLink="false">/projects/ai-pronunciation-trainer</guid>
            <pubDate>Sun, 24 Nov 2024 18:05:00 GMT</pubDate>
            <description><![CDATA[How to self-improve your speech pronunciation in a foreign language]]></description>
            <content:encoded><![CDATA[<h1 id="ai-pronunciation-trainer" tabindex="-1">AI Pronunciation Trainer <a class="header-anchor" href="#ai-pronunciation-trainer" aria-label="Permalink to &quot;AI Pronunciation Trainer&quot;"></a></h1>
<p>In this article, I present the project I am working on: <a href="https://github.com/trincadev/ai-pronunciation-trainer" target="_blank" rel="noreferrer">AI Pronunciation Trainer</a> (online <a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">here</a>), a tool designed to help you improve your pronunciation using the power of artificial intelligence. This project is a refactor of the original <a href="https://github.com/Thiagohgl/ai-pronunciation-trainer" target="_blank" rel="noreferrer">AI Pronunciation Trainer</a> by <a href="https://github.com/Thiagohgl" target="_blank" rel="noreferrer">Thiagohgl</a> to which I have made several improvements to make the tool more effective and easier to use.</p>
<h2 id="what-it-is-and-what-it-does" tabindex="-1">What it is and what it does <a class="header-anchor" href="#what-it-is-and-what-it-does" aria-label="Permalink to &quot;What it is and what it does&quot;"></a></h2>
<p><a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">AI Pronunciation Trainer</a> is a tool that uses AI to evaluate your pronunciation and provide feedback, helping you to improve and be understood more clearly. It leverages the <a href="https://github.com/snakers4/silero-models" target="_blank" rel="noreferrer">Silero STT / TTS</a>, <a href="https://openai.com/index/whisper/" target="_blank" rel="noreferrer">openai whisper</a> and <a href="https://github.com/SYSTRAN/faster-whisper" target="_blank" rel="noreferrer">faster whisper</a> models for speech-to-text functionalities (Silero does also text-to-speech), ensuring accurate and reliable pronunciation assessment.</p>
<h3 id="refactor-upgraded-frontend-and-backend-libraries" tabindex="-1">Refactor: upgraded frontend and backend libraries <a class="header-anchor" href="#refactor-upgraded-frontend-and-backend-libraries" aria-label="Permalink to &quot;Refactor: upgraded frontend and backend libraries&quot;"></a></h3>
<p>About the backend:</p>
<ul>
<li>Updated <a href="https://pytorch.org/" target="_blank" rel="noreferrer">PyTorch</a> at version 2.6.x</li>
<li>Updated Silero German Speech-to-Text model to resolve a bug that prevented the use of PyTorch versions later than 1.13.x</li>
<li>Improved backend tests with the <a href="https://en.wikipedia.org/wiki/Mutation_testing" target="_blank" rel="noreferrer">mutation test suite</a> <a href="https://cosmic-ray.readthedocs.io" target="_blank" rel="noreferrer">Cosmic Ray</a></li>
<li>Fixed a <a href="https://github.com/Thiagohgl/ai-pronunciation-trainer/issues/14" target="_blank" rel="noreferrer">bug</a> with <a href="https://huggingface.co/docs/transformers/model_doc/whisper" target="_blank" rel="noreferrer">whisper</a> not properly transcribing the end timestamp for the last word in the recorded audio (in the end I solved it switching to the <a href="https://pypi.org/project/openai-whisper/" target="_blank" rel="noreferrer">openai whisper python pip package</a>)</li>
<li>Added <a href="https://pypi.org/project/faster-whisper/" target="_blank" rel="noreferrer">faster whisper</a> model support:
<ul>
<li>it avoids <code>None</code> values on <code>end_ts</code> timestamps for the last elements, unlike the HuggingFace Whisper's output</li>
<li>it uses <a href="https://github.com/snakers4/silero-vad" target="_blank" rel="noreferrer">silero-vad</a> to detect long silences within the audio</li>
</ul>
</li>
</ul>
<p>Furthermore, regarding the frontend:</p>
<ul>
<li>Updated the JavaScript libraries using the latest versions of jQuery (3.7.1) and Bootstrap (5.3.3)</li>
<li>New frontend based on <a href="https://gradio.app" target="_blank" rel="noreferrer">Gradio</a> 5.x</li>
<li>Added E2E tests with <a href="https://playwright.dev" target="_blank" rel="noreferrer">Playwright</a></li>
<li>Added the ability to insert custom sentences to read and evaluate</li>
<li>Onboarding tour for new users made with <a href="https://github.com/kamranahmedse/driver.js/" target="_blank" rel="noreferrer">driver.js</a> and <a href="https://www.gradio.app/guides/custom-CSS-and-JS" target="_blank" rel="noreferrer">custom css/javascript in Gradio blocks</a></li>
<li>Playback of individual words in the recording followed by the 'ideal' pronunciation of the same word read by the Text-to-Speech engine</li>
<li>Also added an in-browser Text-to-Speech functionality (on Windows 11, it only works if the English and German language packs are installed)</li>
<li>Custom webApp frontend - improved CSS style on mobile devices</li>
</ul>
<h2 id="online-version-the-huggingface-space-demo" tabindex="-1">Online version: the HuggingFace Space Demo <a class="header-anchor" href="#online-version-the-huggingface-space-demo" aria-label="Permalink to &quot;Online version: the HuggingFace Space Demo&quot;"></a></h2>
<p>You can try it online using the <a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">HuggingFace Space</a>. This online demo allows you to experience the tool's capabilities without any installation or configuration. The HuggingFace Space provides a convenient and accessible way to test the AI Pronunciation Trainer and see how it can help you improve your pronunciation. Please be patient, sometimes it is a bit slow or in sleeping mode (locally it is much faster, especially if you have a powerful computer). Here is an <a href="https://aletrn-ai-pronunciation-trainer.hf.space" target="_blank" rel="noreferrer">embedded version of my HuggingFace Space</a>:</p>
<iframe
 src="https://aletrn-ai-pronunciation-trainer.hf.space"
 frameborder="0"
 allow="microphone"
 referrerpolicy="strict-origin-when-cross-origin"
 style="transform: scale(0.8) translate(-10%); width: 100%; height: 120vh;"
></iframe>
<h2 id="future-work" tabindex="-1">Future Work <a class="header-anchor" href="#future-work" aria-label="Permalink to &quot;Future Work&quot;"></a></h2>
<p>Although this tool works pretty good, there are still some areas for improvement. Here are some of the future enhancements I plan to implement:</p>
<ul>
<li>Receive feedback from the original project author (<a href="https://github.com/Thiagohgl" target="_blank" rel="noreferrer">Thiago Lobato</a>) on my documentation and changes</li>
<li>Ask the original author for explanations on the architectural and functional choices he made</li>
<li>Explore transitioning <a href="https://pytorch.org/" target="_blank" rel="noreferrer">PyTorch</a> to <a href="https://onnxruntime.ai/" target="_blank" rel="noreferrer">onnxruntime</a> (if possible)</li>
<li>Re-add the docker container (if possible)</li>
</ul>
<h2 id="conclusion" tabindex="-1">Conclusion <a class="header-anchor" href="#conclusion" aria-label="Permalink to &quot;Conclusion&quot;"></a></h2>
<p><a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">AI Pronunciation Trainer</a> is a valuable tool for anyone looking to improve their pronunciation. With the power of AI and the improvements made in the refactoring project, this tool provides accurate and reliable feedback to help you speak more clearly and confidently. I invite you to try the <a href="https://huggingface.co/spaces/aletrn/ai-pronunciation-trainer" target="_blank" rel="noreferrer">HuggingFace Space demo</a> and understand how this little project can help you on your journey to better pronunciation.</p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Una giornata di trekking in Val Trela ​​passando per il Lago Nero]]></title>
            <link>/it/photo-galleries/val-trela-20240806</link>
            <guid isPermaLink="false">/it/photo-galleries/val-trela-20240806</guid>
            <pubDate>Tue, 06 Aug 2024 21:25:00 GMT</pubDate>
            <description><![CDATA[Una giornata di trekking in Val Trela ​​passando per il Lago Nero]]></description>
            <content:encoded><![CDATA[<h1 id="val-trela-6-august-2024" tabindex="-1">Val Trela, 6 August 2024 <a class="header-anchor" href="#val-trela-6-august-2024" aria-label="Permalink to &quot;Val Trela, 6 August 2024&quot;"></a></h1>
<h2 id="una-giornata-di-trekking-in-val-trela-​​passando-per-il-lago-nero" tabindex="-1">Una giornata di trekking in Val Trela ​​passando per il Lago Nero <a class="header-anchor" href="#una-giornata-di-trekking-in-val-trela-​​passando-per-il-lago-nero" aria-label="Permalink to &quot;Una giornata di trekking in Val Trela ​​passando per il Lago Nero&quot;"></a></h2>
<div id="map-container">
    <MapWithPolyline
      :center='[46.52092705, 10.210349943]'
      location="Val Trela"
      :mapName="mapName"
      :pathUrl="`/paths/${mapName}.json`"
      :photosUrl="`/photos/${mapName}.json`"
      zoom=13
    />
</div>
<h2 id="alcuni-angoli-della-val-trela-e-del-lago-nero" tabindex="-1">Alcuni angoli della Val Trela e del Lago Nero <a class="header-anchor" href="#alcuni-angoli-della-val-trela-e-del-lago-nero" aria-label="Permalink to &quot;Alcuni angoli della Val Trela e del Lago Nero&quot;"></a></h2>
<p>Photos taken on 6 August 2024.</p>
<div id="image-gallery-container">
    <SimpleGallery
      :galleryID="mapName"
      :imagesData="imagesData"
    />
</div>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[A Trekking day in Val Trela passing by Lago Nero]]></title>
            <link>/photo-galleries/val-trela-20240806</link>
            <guid isPermaLink="false">/photo-galleries/val-trela-20240806</guid>
            <pubDate>Tue, 06 Aug 2024 21:25:00 GMT</pubDate>
            <description><![CDATA[A Trekking day in Val Trela passing by Lago Nero]]></description>
            <content:encoded><![CDATA[<h1 id="val-trela-6-august-2024" tabindex="-1">Val Trela, 6 August 2024 <a class="header-anchor" href="#val-trela-6-august-2024" aria-label="Permalink to &quot;Val Trela, 6 August 2024&quot;"></a></h1>
<h2 id="a-trekking-day-in-val-trela-passing-by-lago-nero" tabindex="-1">A Trekking day in Val Trela passing by Lago Nero <a class="header-anchor" href="#a-trekking-day-in-val-trela-passing-by-lago-nero" aria-label="Permalink to &quot;A Trekking day in Val Trela passing by Lago Nero&quot;"></a></h2>
<div id="map-container">
    <MapWithPolyline
      :center='[46.52092705, 10.210349943]'
      location="Val Trela"
      :mapName="mapName"
      :pathUrl="`/paths/${mapName}.json`"
      :photosUrl="`/photos/${mapName}.json`"
      zoom=13
    />
</div>
<h2 id="different-sides-of-val-trela-and-lago-nero" tabindex="-1">Different sides of Val Trela and Lago Nero <a class="header-anchor" href="#different-sides-of-val-trela-and-lago-nero" aria-label="Permalink to &quot;Different sides of Val Trela and Lago Nero&quot;"></a></h2>
<p>Photos taken on 6 August 2024.</p>
<div id="image-gallery-container">
    <SimpleGallery
      :galleryID="mapName"
      :imagesData="imagesData"
    />
</div>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[LISA+SamGIS adattato ad hardware HuggingFace ZeroGPU]]></title>
            <link>/it/projects/lisa-samgis-on-zerogpu-huggingface</link>
            <guid isPermaLink="false">/it/projects/lisa-samgis-on-zerogpu-huggingface</guid>
            <pubDate>Sun, 14 Jul 2024 00:24:00 GMT</pubDate>
            <description><![CDATA[Scopri come LISA+SamGIS funziona su hardware ZeroGPU]]></description>
            <content:encoded><![CDATA[<h1 id="lisa-samgis-adattato-ad-hardware-huggingface-zerogpu" tabindex="-1">LISA+SamGIS adattato ad hardware HuggingFace ZeroGPU <a class="header-anchor" href="#lisa-samgis-adattato-ad-hardware-huggingface-zerogpu" aria-label="Permalink to &quot;LISA+SamGIS adattato ad hardware HuggingFace ZeroGPU&quot;"></a></h1>
<p>Per una comprensione di base del mio progetto, si veda <a href="/it/projects/samgis-segment-anything-applied-to-GIS.html">questa</a> e <a href="/it/projects/lisa-adapted-for-samgis.html">questa pagina</a>.</p>
<p>Oggi invece sto scrivendo della mia nuova demo utilizzando un hardware <a href="https://huggingface.co/zero-gpu-explorers" target="_blank" rel="noreferrer">ZeroGPU</a>. Si noti che <a href="https://huggingface.co/zero-gpu-explorers" target="_blank" rel="noreferrer">ZeroGPU Spaces</a> è attualmente in versione beta. Gli utenti <a href="https://huggingface.co/subscribe/pro" target="_blank" rel="noreferrer">PRO</a> o le <a href="https://huggingface.co/enterprise" target="_blank" rel="noreferrer">Enterprise organizations</a> possono creare i propri space ZeroGPU a loro nome. Inoltre è necessario pagare ogni mese per mantenere il diritto di utilizzare l'hardware ZeroGPU.</p>
<ul>
<li>Ho riscontrato inizialmente dei problemi causati dall’uso del decoratore <code>spaces.GPU</code> su una funzione inappropriata la cui esecuzione richiedeva troppo tempo, causando timeout. Risolto facendo debug per usare il decoratore solo sulle funzioni che ne richiedevano effettivamente l’uso.</li>
<li>Frontend custom: non mi piace molto <a href="https://svelte.dev/" target="_blank" rel="noreferrer">svelte</a> (la libreria js scelta dal team di Gradio) ma soprattutto ho già un progetto ben avviato scritto in <a href="https://vuejs.org/" target="_blank" rel="noreferrer">vuejs</a> e <a href="https://vitejs.dev/" target="_blank" rel="noreferrer">vite</a> che voglio riutilizzare. Risolto facendo <a href="https://huggingface.co/docs/hub/spaces-dependencies" target="_blank" rel="noreferrer">l’installazione del pacchetto Debian</a> nodejs 18 per poi installare le dipendenze e fare la build del progetto nodejs direttamente da dentro il file <code>app.py</code> usando <code>subpropcess.run()</code>.</li>
</ul>
<p>Nota che sto usando un periodo di timeout di 48 ore prima di mettere in pausa il mio space. Qualsiasi interazione successiva potrebbe richiedere un po' di tempo prima che lo space riparta.</p>
<p>Ultimo, ma non ultimo, la pagina della demo è online <a href="https://huggingface.co/spaces/aletrn/samgis-lisa-on-zero" target="_blank" rel="noreferrer">qui (interfaccia Gradio)</a> e <a href="https://aletrn-samgis-lisa-on-zero.hf.space/lisa" target="_blank" rel="noreferrer">qui (la mia pagina SPA custom)</a>.</p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[LISA+SamGIS on ZeroGPU HuggingFace]]></title>
            <link>/projects/lisa-samgis-on-zerogpu-huggingface</link>
            <guid isPermaLink="false">/projects/lisa-samgis-on-zerogpu-huggingface</guid>
            <pubDate>Sun, 14 Jul 2024 00:24:00 GMT</pubDate>
            <description><![CDATA[Learn how LISA+SamGIS has been adapted for ZeroGPU hardware]]></description>
            <content:encoded><![CDATA[<h1 id="lisa-samgis-on-zerogpu-huggingface-hardware" tabindex="-1">LISA+SamGIS on ZeroGPU HuggingFace hardware <a class="header-anchor" href="#lisa-samgis-on-zerogpu-huggingface-hardware" aria-label="Permalink to &quot;LISA+SamGIS on ZeroGPU HuggingFace hardware&quot;"></a></h1>
<p>See <a href="/projects/samgis-segment-anything-applied-to-GIS.html">this</a> and <a href="/projects/lisa-adapted-for-samgis.html">this page</a> for a basic understand of what is about my project.</p>
<p>Today instead I'm writing about my new demo on <a href="https://huggingface.co/zero-gpu-explorers" target="_blank" rel="noreferrer">ZeroGPU</a> space. Note that <a href="https://huggingface.co/zero-gpu-explorers" target="_blank" rel="noreferrer">ZeroGPU Spaces</a> is currently in beta. <a href="https://huggingface.co/subscribe/pro" target="_blank" rel="noreferrer">PRO</a> users or <a href="https://huggingface.co/enterprise" target="_blank" rel="noreferrer">Enterprise organizations</a> can host their own ZeroGPU Spaces under their namespaces. Also there is need to pay every month for keep the right to use ZeroGPU hardware.</p>
<ul>
<li>I solved some problems caused by <code>spaces.GPU</code> decorator on a function which execution time was too high, causing a timeout. To solve it I started debugging and I ended using <code>spaces.GPU</code> only on functions that really needed the GPU acceleration.</li>
<li>I don't like very much <a href="https://svelte.dev/" target="_blank" rel="noreferrer">svelte</a> (the js library chosen by Gradio team) and I already have a <a href="https://vuejs.org/" target="_blank" rel="noreferrer">vuejs</a>/<a href="https://vitejs.dev/" target="_blank" rel="noreferrer">vite</a> frontend project that I can re-use. I solved this installing the nodejs 18 <a href="https://huggingface.co/docs/hub/spaces-dependencies" target="_blank" rel="noreferrer">Debian package</a> and starting the nodejs build from within the <code>app.py</code> file using <code>subpropcess.run()</code>.</li>
</ul>
<p>Note that I'm using a timeout period of 48h before putting my space in pause. Any interaction after that could take a while until the space restart.</p>
<p>Last but not least there is my online demo <a href="https://huggingface.co/spaces/aletrn/samgis-lisa-on-zero" target="_blank" rel="noreferrer">here (Gradio interface)</a> and <a href="https://aletrn-samgis-lisa-on-zero.hf.space/lisa" target="_blank" rel="noreferrer">here (my custom SPA page)</a>.</p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[What I learnt from development on LISA with SamGIS (So far)]]></title>
            <link>/blog/what-I-learnt-from-lisa-with-samgis</link>
            <guid isPermaLink="false">/blog/what-I-learnt-from-lisa-with-samgis</guid>
            <pubDate>Fri, 26 Apr 2024 00:50:56 GMT</pubDate>
            <description><![CDATA[Recommendations based on integrative development of LISA and SamGIS]]></description>
            <content:encoded><![CDATA[<h1 id="what-i-learnt-from-development-on-lisa-with-samgis-so-far" tabindex="-1">What I learnt from development on LISA with SamGIS (So far) <a class="header-anchor" href="#what-i-learnt-from-development-on-lisa-with-samgis-so-far" aria-label="Permalink to &quot;What I learnt from development on LISA with SamGIS (So far)&quot;"></a></h1>
<h2 id="read-publications-related-to-the-projects-i-work-on" tabindex="-1">Read publications related to the projects I work on <a class="header-anchor" href="#read-publications-related-to-the-projects-i-work-on" aria-label="Permalink to &quot;Read publications related to the projects I work on&quot;"></a></h2>
<p>To improve my understanding of my machine learning project I decided to read the papers on which <a href="https://arxiv.org/abs/2308.00692" target="_blank" rel="noreferrer">LISA</a> and <a href="https://arxiv.org/abs/2304.02643" target="_blank" rel="noreferrer">Segment Anything</a> are based. Besides some theoretical informations about LLM, I noticed that the modular architecture of &quot;SAM&quot; permits to save and re-use image embeddings. Since SamGIS didn't work this way initially, I formulated an hypothesis about this.</p>
<h2 id="debugging-measures-and-optimization-image-embedding-hypothesis" tabindex="-1">Debugging, measures and optimization: Image Embedding Hypothesis <a class="header-anchor" href="#debugging-measures-and-optimization-image-embedding-hypothesis" aria-label="Permalink to &quot;Debugging, measures and optimization: Image Embedding Hypothesis&quot;"></a></h2>
<p>At this point I continued my debugging work by measuring the duration of individual steps during the execution of SamGIS functions. Creating an image embedding is quite an expensive operation, so it is advantageous saving it and re-using it (I verified that implementing my hypothesis would improve the performance of the software). Using the HuggingFace hardware profile &quot;Nvidia T4 Small&quot; (with 4 vCPU, 15 GB RAM and 16 GB VRAM) it's possible to save almost 1 second on every inference after the first, using the same image (without change the geographical area tiles provider).</p>
<h2 id="the-role-of-llms-with-prompts-having-different-characteristics" tabindex="-1">The role of LLMs with prompts having different characteristics <a class="header-anchor" href="#the-role-of-llms-with-prompts-having-different-characteristics" aria-label="Permalink to &quot;The role of LLMs with prompts having different characteristics&quot;"></a></h2>
<p>LISA inherits the language generation capabilities of multi-modal LLMs such as <a href="https://llava-vl.github.io/" target="_blank" rel="noreferrer">Llava</a>. These models excel at handling complex reasoning, world knowledge, explanatory answers and multi-turn conversations. They’re powerful tools for bridging the gap between text and visual understanding.</p>
<p>LISA allows you to perform <a href="/projects/lisa-adapted-for-samgis.html#some-input-text-prompts-with-their-geojson-outputs">rather complex reasoning</a> during image segmentation (e.g. &quot;identify the houses near the trees...&quot; vs &quot;identify the houses...&quot;) without any particular performance degradation. On the contrary, requests containing the explanation of reason (&quot;explain why&quot;) the segmentation task is done in a certain way will have much higher execution times (in the order of minutes).</p>
<p>There are <a href="/projects/lisa-adapted-for-samgis.html#duration-of-segmentation-tasks">more details here</a> about these improvements following the changes described and regarding different performance due to different cases when using SamGIS with LISA.</p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Cosa ho imparato durante lo sviluppo di SamGIS con LISA (finora)]]></title>
            <link>/it/blog/what-I-learnt-from-lisa-with-samgis</link>
            <guid isPermaLink="false">/it/blog/what-I-learnt-from-lisa-with-samgis</guid>
            <pubDate>Fri, 26 Apr 2024 00:50:56 GMT</pubDate>
            <description><![CDATA[Consigli basati sullo sviluppo integrativo di SamGIS e LISA]]></description>
            <content:encoded><![CDATA[<h1 id="cosa-ho-imparato-durante-lo-sviluppo-di-samgis-con-lisa-finora" tabindex="-1">Cosa ho imparato durante lo sviluppo di SamGIS con LISA (finora) <a class="header-anchor" href="#cosa-ho-imparato-durante-lo-sviluppo-di-samgis-con-lisa-finora" aria-label="Permalink to &quot;Cosa ho imparato durante lo sviluppo di SamGIS con LISA (finora)&quot;"></a></h1>
<h2 id="leggere-le-pubblicazioni-inerenti-ai-progetti-su-cui-lavoro" tabindex="-1">Leggere le pubblicazioni inerenti ai progetti su cui lavoro <a class="header-anchor" href="#leggere-le-pubblicazioni-inerenti-ai-progetti-su-cui-lavoro" aria-label="Permalink to &quot;Leggere le pubblicazioni inerenti ai progetti su cui lavoro&quot;"></a></h2>
<p>Per migliorare la mia comprensione del mio progetto di machine learning ho deciso di leggere l'articolo su cui si basano LISA e Segment Anything. Oltre ad alcune informazioni teoriche su LLM, ho notato che l'architettura modulare di &quot;SAM&quot; consente di creare e riutilizzare gli image embedding. Dato che SamGIS non funzionava in questo modo inizialmente, ho formulato un'ipotesi al riguardo.</p>
<h2 id="debug-misure-ed-ottimizzazione-ipotesi-sul-image-embedding" tabindex="-1">Debug, misure ed ottimizzazione: ipotesi sul image embedding <a class="header-anchor" href="#debug-misure-ed-ottimizzazione-ipotesi-sul-image-embedding" aria-label="Permalink to &quot;Debug, misure ed ottimizzazione: ipotesi sul image embedding&quot;"></a></h2>
<p>A questo punto ho continuato il mio lavoro di debug misurando la durata dei singoli passaggi durante l'esecuzione delle funzioni di SamGIS. La creazione di un image embedding è un'operazione abbastanza onerosa, quindi è vantaggioso salvarlo e riutilizzarlo (ho verificato implementare la mia ipotesi migliorerebbe le prestazioni del software). Utilizzando il profilo hardware HuggingFace &quot;Nvidia T4 Small&quot; (con 4 vCPU, 15 GB RAM e 16 GB VRAM) è possibile risparmiare circa 1 secondo per ogni inferenza successiva alla prima, utilizzando la stessa immagine (quindi senza modificare il tile provider e l'area geografica).</p>
<h2 id="il-ruolo-dei-llm-con-prompt-aventi-differenti-caratteristiche" tabindex="-1">Il ruolo dei LLM con prompt aventi differenti caratteristiche <a class="header-anchor" href="#il-ruolo-dei-llm-con-prompt-aventi-differenti-caratteristiche" aria-label="Permalink to &quot;Il ruolo dei LLM con prompt aventi differenti caratteristiche&quot;"></a></h2>
<p>LISA eredita le capacità di generazione del linguaggio dei LLM multi-modali come <a href="https://llava-vl.github.io/" target="_blank" rel="noreferrer">Llava</a>. Questi modelli eccellono nella gestione di ragionamenti complessi, conoscenza del mondo, risposte esplicative e conversazioni a più turni. Sono strumenti potenti per colmare il divario tra testo e comprensione visiva.</p>
<p>LISA permette di effettuare <a href="/it/projects/lisa-adapted-for-samgis.html#prompts-testuali-d-input-e-relativi-geojson-di-output">ragionamenti piuttosto complessi</a> durante la segmentazione delle immagini (es. &quot;identify the houses near the trees...&quot; vs &quot;identify the houses...&quot;) senza particolari peggioramenti prestazionali. Al contrario, richieste contenenti la spiegazione del motivo (&quot;explain why&quot;) per cui il task di segmentazione sia fatto in un certo modo avranno tempi di esecuzione molto più elevati (nell'ordine di minuti).</p>
<p>Sono disponibili <a href="/it/projects/lisa-adapted-for-samgis.html#durata-dei-task-di-segmentazione">maggiori dettagli qui</a> su questi miglioramenti in seguito alle modifiche descritte e relativamente alle differenti prestazioni dovute a diversi casi durante l'utilizzo di SamGIS con LISA.</p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[SamGIS - Some notes about Segment Anything]]></title>
            <link>/blog/notes-about-segment-anything</link>
            <guid isPermaLink="false">/blog/notes-about-segment-anything</guid>
            <pubDate>Wed, 17 Apr 2024 19:02:00 GMT</pubDate>
            <description><![CDATA[Some notes about the architecture of Segment Anything machine learning model]]></description>
            <content:encoded><![CDATA[<h1 id="samgis-some-notes-about-segment-anything" tabindex="-1">SamGIS - Some notes about Segment Anything <a class="header-anchor" href="#samgis-some-notes-about-segment-anything" aria-label="Permalink to &quot;SamGIS - Some notes about Segment Anything&quot;"></a></h1>
<h2 id="from-the-segment-anything-paper" tabindex="-1">From the <a href="https://arxiv.org/abs/2304.02643" target="_blank" rel="noreferrer">Segment Anything paper</a> <a class="header-anchor" href="#from-the-segment-anything-paper" aria-label="Permalink to &quot;From the [Segment Anything paper](https://arxiv.org/abs/2304.02643)&quot;"></a></h2>
<p>&quot;<a href="https://github.com/facebookresearch/segment-anything" target="_blank" rel="noreferrer">SAM</a>&quot; is a <a href="https://aws.amazon.com/what-is/foundation-models/" target="_blank" rel="noreferrer">foundation model</a> aiming for performing &quot;zero-shot&quot; image segmentation:</p>
<ul>
<li>it's build and trained with a large image dataset with a massive amount of segmentation masks</li>
<li>the SAM team propose the &quot;promptable&quot; segmentation task, where the goal is to return a valid segmentation mask given any segmentation prompt.</li>
</ul>
<p>Since this model should perform &quot;zero-shot&quot; segmentation the model must support flexible prompts, needs to compute masks in amortized real-time to allow interactive use and must be ambiguity-aware. That's the model architecture:</p>
<ol>
<li>source 1: an image encoder computes an image embedding</li>
<li>source 2: a fast prompt encoder embeds prompts</li>
<li>output: a fast mask decoder combines these two sources to predict segmentation masks</li>
</ol>
<p>Because annotation masks are not abundant online, especially of high quality, the SAM developers opted for developing a &quot;data engine&quot;, developing both the model and the dataset annotations (from manual stage to semi-automated to fully automated). Images in SA-1B span a geographically and economically diverse set of countries and we found that SAM performs similarly across different groups of people.</p>
<h3 id="segment-anything-tasks" tabindex="-1">Segment Anything Tasks <a class="header-anchor" href="#segment-anything-tasks" aria-label="Permalink to &quot;Segment Anything Tasks&quot;"></a></h3>
<h4 id="task" tabindex="-1">Task <a class="header-anchor" href="#task" aria-label="Permalink to &quot;Task&quot;"></a></h4>
<p>Here SAM team translate prompts from NLP to segmentation (selecting/de-selecting points, box, mask, free-form text). Like a language model should output a coherent response to an ambiguous prompt, the promptable segmentation task should return a valid segmentation mask given any prompt.</p>
<h4 id="pre-training" tabindex="-1">Pre-Training <a class="header-anchor" href="#pre-training" aria-label="Permalink to &quot;Pre-Training&quot;"></a></h4>
<p>The promptable segmentation task suggests a natural pre-training algorithm that simulates a sequence of prompts (e.g., points, boxes, masks) for each training sample and compares the model’s mask predictions against the ground truth.</p>
<h3 id="segment-anything-model" tabindex="-1">Segment Anything Model <a class="header-anchor" href="#segment-anything-model" aria-label="Permalink to &quot;Segment Anything Model&quot;"></a></h3>
<h4 id="image-encoder" tabindex="-1">Image encoder <a class="header-anchor" href="#image-encoder" aria-label="Permalink to &quot;Image encoder&quot;"></a></h4>
<p>The algorithm use a MAE (<a href="https://arxiv.org/abs/2111.06377" target="_blank" rel="noreferrer">&quot;Masked Autoencoders Are Scalable Vision Learners&quot;</a>) pre-trained Vision Transformer (<a href="https://arxiv.org/abs/2010.11929" target="_blank" rel="noreferrer">ViT</a>) minimally adapted to process <a href="https://arxiv.org/abs/2203.16527" target="_blank" rel="noreferrer">high resolution inputs</a>.</p>
<h4 id="prompt-encoder" tabindex="-1">Prompt encoder <a class="header-anchor" href="#prompt-encoder" aria-label="Permalink to &quot;Prompt encoder&quot;"></a></h4>
<p>SAM supports two sets of prompts:</p>
<ul>
<li>sparse (points, boxes, text)</li>
<li>dense (masks)</li>
</ul>
<p>SAM prompts <a href="https://arxiv.org/abs/2006.10739" target="_blank" rel="noreferrer">handle points and boxes by positional encodings</a> summed with <a href="https://arxiv.org/abs/2103.00020" target="_blank" rel="noreferrer">learned embeddings for each prompt type</a>. Dense prompts (i.e., masks) are embedded using convolutions and summed element-wise with the image embedding.</p>
<h4 id="mask-decoder" tabindex="-1">Mask decoder <a class="header-anchor" href="#mask-decoder" aria-label="Permalink to &quot;Mask decoder&quot;"></a></h4>
<p>The mask decoder efficiently maps the image embedding, prompt embeddings, and an output token to a mask. This design employs a modification of a <a href="https://arxiv.org/abs/1706.03762" target="_blank" rel="noreferrer">Transformer decoder block</a> followed by a dynamic mask prediction head. The decoder block uses prompt self-attention and cross-attention in two directions (prompt-to-image embedding and vice-versa) to update all embeddings. After running two blocks, the procedure upsample the image embedding and an MLP maps the output token to a dynamic linear classifier, which then computes the mask foreground probability at each image location.</p>
<h4 id="resolving-ambiguity" tabindex="-1">Resolving ambiguity <a class="header-anchor" href="#resolving-ambiguity" aria-label="Permalink to &quot;Resolving ambiguity&quot;"></a></h4>
<p>With one output, to avoid masks merging in case of an ambiguous prompt the model can predict more than one output mask for a single prompt. 3 masks should address most common cases (nested masks are often at most three deep: whole, part, and subpart). During training, the procedure backprops only the minimum loss over masks. To rank masks, the model predicts a confidence score (i.e., estimated IoU) for each mask.</p>
<h2 id="about-image-embedding-re-use-and-samgis" tabindex="-1">About image embedding re-use and SamGIS <a class="header-anchor" href="#about-image-embedding-re-use-and-samgis" aria-label="Permalink to &quot;About image embedding re-use and SamGIS&quot;"></a></h2>
<p>After reading this paper I understood that I could improve SamGIS software design storing and re-using the image embeddings.</p>
<p>I implemented this change in <a href="https://docs.ml-trinca.tornidor.com/#version-1-3-0" target="_blank" rel="noreferrer">SamGIS version 1.3.0</a>. Some test data from the <a href="https://huggingface.co/spaces/aletrn/samgis" target="_blank" rel="noreferrer">SamGIS demo</a> I used:</p>
<ul>
<li>first request: 5.42s
<ul>
<li>instantiated <a href="https://github.com/CASIA-IVA-Lab/FastSAM" target="_blank" rel="noreferrer">fastsam</a> model</li>
<li>created image from webmap (I'm using <a href="https://www.openstreetmap.org/" target="_blank" rel="noreferrer">OpenStreetMap</a> as tiles provider and Mapnik as map layer)</li>
<li>created image embedding</li>
</ul>
</li>
<li>second request: 0.41s</li>
<li>from third to seventh request: ~0.34s</li>
</ul>
<p>Note that making one request immediately after another keep requests duration low probably because of cache during tiles download on backend side. Instead waiting more than 10 minutes it seems invalidate the cache, then <a href="https://github.com/geopandas/contextily" target="_blank" rel="noreferrer">contextily</a> (the GeoPandas' library that I use as a tiles client) added from 0.5s to 1.5s of time, during my tests, to download the tiles.</p>
<details>
    <summary><i>Click here to show my test request payload</i></summary>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "bbox"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">        "ne"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.236615111857255</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.519996643066408</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        },</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">        "sw"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.13405108959001</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.29821014404297</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "prompt"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">146</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">                "lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.18483299780137</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">                "lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.418864745562386</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            },</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    ],</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "zoom"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">13</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "source_type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"OpenStreetMap"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
</div></details>
<h2 id="about-zero-shot-text-to-mask-lisa-and-samgis" tabindex="-1">About Zero-Shot Text-to-Mask: LISA and SamGIS <a class="header-anchor" href="#about-zero-shot-text-to-mask-lisa-and-samgis" aria-label="Permalink to &quot;About Zero-Shot Text-to-Mask: LISA and SamGIS&quot;"></a></h2>
<p>SAM can use also simple free-form text prompts. For a practical use of this feature, see:</p>
<ul>
<li><a href="https://github.com/IDEA-Research/Grounded-Segment-Anything" target="_blank" rel="noreferrer">Grounded-SAM</a></li>
<li><a href="https://github.com/dvlab-research/LISA" target="_blank" rel="noreferrer">LISA</a></li>
</ul>
<p>Of course could be of your interest also my <a href="https://trinca.tornidor.com/projects/lisa-adapted-for-samgis" target="_blank" rel="noreferrer">integration work of LISA with SamGIS</a> and its <a href="https://huggingface.co/spaces/aletrn/samgis-lisa-on-cuda" target="_blank" rel="noreferrer">cuda demo</a>. I need to keep it paused because of cost, but I am requesting the use of a free GPU from HuggingFace.</p>
<p>Right now there is a <a href="https://huggingface.co/spaces/aletrn/samgis-lisa-on-zero/" target="_blank" rel="noreferrer">demo hardware-based ZeroGPU</a>: it's a little bit slow compared with the regular <a href="https://huggingface.co/spaces/aletrn/samgis-lisa-on-cuda" target="_blank" rel="noreferrer">cuda demo</a>, but it's free to use (if I keep to pay the fee for <a href="https://huggingface.co/pricing#pro" target="_blank" rel="noreferrer">PRO HuggingFace subscription</a>).</p>
<p>If you like my <a href="https://huggingface.co/spaces/aletrn/samgis-lisa-on-cuda" target="_blank" rel="noreferrer">project</a>, please like or comment on the <a href="https://huggingface.co/spaces/aletrn/samgis-lisa-on-cuda/discussions/1" target="_blank" rel="noreferrer">HuggingFace GPU resource request thread</a>.</p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[SamGIS - Alcuni appunti su Segment Anything]]></title>
            <link>/it/blog/notes-about-segment-anything</link>
            <guid isPermaLink="false">/it/blog/notes-about-segment-anything</guid>
            <pubDate>Wed, 17 Apr 2024 19:02:00 GMT</pubDate>
            <description><![CDATA[Alcune note sull'architettura del modello di machine learning Segment Anything]]></description>
            <content:encoded><![CDATA[<h1 id="samgis-alcuni-appunti-su-segment-anything" tabindex="-1">SamGIS - Alcuni appunti su Segment Anything <a class="header-anchor" href="#samgis-alcuni-appunti-su-segment-anything" aria-label="Permalink to &quot;SamGIS - Alcuni appunti su Segment Anything&quot;"></a></h1>
<p>Rimando alle mie note in inglese su <a href="/blog/notes-about-segment-anything.html">Segment Anything</a>.</p>
<h2 id="a-proposito-del-riutilizzo-degli-embedding-delle-immagini-e-samgis" tabindex="-1">A proposito del riutilizzo degli embedding delle immagini e SamGIS <a class="header-anchor" href="#a-proposito-del-riutilizzo-degli-embedding-delle-immagini-e-samgis" aria-label="Permalink to &quot;A proposito del riutilizzo degli embedding delle immagini e SamGIS&quot;"></a></h2>
<p>Dopo aver riletto questo paper ho capito che avrei potuto migliorare l'efficienza di SamGIS conservando e riutilizzando gli embedding delle immagini.</p>
<p>Ho implementato questa modifica in <a href="https://docs.ml-trinca.tornidor.com/#version-1-3-0" target="_blank" rel="noreferrer">SamGIS versione 1.3.0</a>. Alcuni dati di test dalla <a href="https://huggingface.co/spaces/aletrn/samgis" target="_blank" rel="noreferrer">demo SamGIS</a> che ho utilizzato:</p>
<ul>
<li>prima chiamata: 5.42s
<ul>
<li>modello <a href="https://github.com/CASIA-IVA-Lab/FastSAM" target="_blank" rel="noreferrer">fastsam</a> istanziato</li>
<li>immagine creata dalla mappa web (uso OpenStreetMap come tile provider e Mapnik come layer della webmap)</li>
<li>creato il embedding dell'immagine</li>
</ul>
</li>
<li>seconda chiamata: 0,41 s</li>
<li>dalla terza alla settima chiamata: ~0,34s</li>
</ul>
<p>Si tenga presente che effettuando una chiamata immediatamente dopo l'altra la durata rimane bassa, probabilmente a causa dell'utilizzo della cache durante il download delle tile nel back-end. Aspettando più di 10 minuti sembra invalidare la cache, quindi <a href="https://github.com/geopandas/contextily" target="_blank" rel="noreferrer">contextily</a> (la libreria di GeoPanda che utilizzo come client di Tiles) ha impiegato da 0.5s a 1.5s di tempo, durante le mie prove, per il download delle tile.</p>
<details>
    <summary><i>Espandere qui per il dettaglio del payload delle chiamate di test.</i></summary>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "bbox"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">        "ne"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.236615111857255</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.519996643066408</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        },</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">        "sw"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.13405108959001</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.29821014404297</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "prompt"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">146</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">                "lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.18483299780137</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">                "lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.418864745562386</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            },</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    ],</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "zoom"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">13</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "source_type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"OpenStreetMap"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
</div></details>
<h2 id="a-proposito-della-conversione-dal-testo-alla-maschera-zero-shot-lisa-e-samgis" tabindex="-1">A proposito della conversione &quot;dal testo alla maschera&quot; &quot;zero shot&quot;: LISA e SamGIS <a class="header-anchor" href="#a-proposito-della-conversione-dal-testo-alla-maschera-zero-shot-lisa-e-samgis" aria-label="Permalink to &quot;A proposito della conversione &quot;dal testo alla maschera&quot; &quot;zero shot&quot;: LISA e SamGIS&quot;"></a></h2>
<p>La versione originale di SAM può utilizzare anche semplici prompt testuali in linguaggio naturale. Per un uso pratico di questa funzionalità, si veda:</p>
<ul>
<li><a href="https://github.com/IDEA-Research/Grounded-Segment-Anything" target="_blank" rel="noreferrer">Grounded-SAM</a></li>
<li><a href="https://github.com/dvlab-research/LISA" target="_blank" rel="noreferrer">LISA</a></li>
</ul>
<p>Naturalmente potrebbe interessare anche il mio <a href="https://trinca.tornidor.com/projects/lisa-adapted-for-samgis" target="_blank" rel="noreferrer">lavoro di integrazione di LISA con SamGIS</a> e la corrispondente [demo](<a href="https://huggingface" target="_blank" rel="noreferrer">https://huggingface</a>. co/spaces/aletrn/samgis-lisa-on-cuda). Devo tenerlo in pausa a causa dei costi, ma sto richiedendo l'uso di una GPU gratuita da HuggingFace.</p>
<p>Al momento è disponibile una <a href="https://huggingface.co/spaces/aletrn/samgis-lisa-on-zero/" target="_blank" rel="noreferrer">demo basata su hardware ZeroGPU</a>: è un po' lenta rispetto alla <a href="https://huggingface.co/spaces/aletrn/samgis-lisa-on-cuda" target="_blank" rel="noreferrer">demo cuda</a> classica, ma è gratuita (nel caso io continui a pagare la quota per l'<a href="https://huggingface.co/pricing#pro" target="_blank" rel="noreferrer">abbonamento PRO HuggingFace</a>).</p>
<p>Nel caso il mio <a href="https://huggingface.co/spaces/aletrn/samgis-lisa-on-cuda" target="_blank" rel="noreferrer">progetto</a> fosse interessante, metti &quot;mi piace&quot; o commenta il [thread di richiesta di risorse GPU di HuggingFace](<a href="https://huggingface.co" target="_blank" rel="noreferrer">https://huggingface.co</a> /spaces/aletrn/samgis-lisa-on-cuda/discussions/1).</p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[LISA integrato in SamGIS]]></title>
            <link>/it/projects/lisa-adapted-for-samgis</link>
            <guid isPermaLink="false">/it/projects/lisa-adapted-for-samgis</guid>
            <pubDate>Mon, 08 Apr 2024 23:24:00 GMT</pubDate>
            <description><![CDATA[Scopri come LIS (Reasoning Segmentation via Large Language Model) è stato adattato per l'integrazione con SamGIS]]></description>
            <content:encoded><![CDATA[<h1 id="lisa-integrato-in-samgis" tabindex="-1">LISA integrato in SamGIS <a class="header-anchor" href="#lisa-integrato-in-samgis" aria-label="Permalink to &quot;LISA integrato in SamGIS&quot;"></a></h1>
<p>La segmentazione d'immagine è un compito cruciale nella visione artificiale, dove l'obiettivo di fare <a href="https://www.ibm.com/topics/instance-segmentation" target="_blank" rel="noreferrer">&quot;instance segmentation&quot;</a> di un dato oggetto. Ho già lavorato ad un progetto, <a href="/it/projects/samgis-segment-anything-applied-to-GIS.html">SamGIS</a>, a riguardo. Un passo logico successivo sarebbe integrare la capacità di riconoscere gli oggetti attraverso prompt testuali. Quest'attività apparentemente semplice in effetti comporta però delle differenze rispetto a quanto fatto in SamGIS che utilizza <a href="https://segment-anything.com/" target="_blank" rel="noreferrer">Segment Anything</a> (il backend di machine learning usato da SamGIS). Mentre infatti &quot;SAM&quot; non categorizza ciò che identifica, partire da un prompt scritto necessita della conoscenza di quali classi di oggetti esistano nell'immagine in analisi. Un <a href="https://arxiv.org/abs/2305.11175" target="_blank" rel="noreferrer">modello di linguaggio visivo</a> (o VLM) che funziona bene per questo compito è <a href="https://github.com/dvlab-research/LISA" target="_blank" rel="noreferrer">LISA</a>. Gli autori di LISA hanno basato il loro lavoro su <a href="https://segment-anything.com/" target="_blank" rel="noreferrer">Segment Anything</a> e <a href="https://llava-vl.github.io/" target="_blank" rel="noreferrer">Llava</a>, un LLM con capacità multimodali (può elaborare sia istruzioni di testo che immagini). Sfruttando le capacità di &quot;segmentazione ragionata&quot; di LISA, SamGIS può eseguire analisi di tipo &quot;zero-shot&quot;, ovvero senza addestramento pregresso specifico e specialistico in ambito geologico, geomorfologico o fotogrammetrico.</p>
<h2 id="prompts-testuali-d-input-e-relativi-geojson-di-output" tabindex="-1">Prompts testuali d'input e relativi geojson di output <a class="header-anchor" href="#prompts-testuali-d-input-e-relativi-geojson-di-output" aria-label="Permalink to &quot;Prompts testuali d'input e relativi geojson di output&quot;"></a></h2>
  <div>
    <MapWithPolygonComponent
      :center="[46.17, 10.075]"
      currentInputOutputJson="houses"
      imageOverlayUrl="/projects/teglio-somassa.jpg"
      :imageBounds="[[46.173968917056655, 10.082219839096071], [46.16651671595163, 10.066105127334597]]"
      prefix="projects/teglio-somassa"
      :zoom=15
    />
  </div>
<p>Ho esportato l'immagine in overlay usando come sorgente il provider di tiles Esri.WorldImagery.</p>
<p>Si noti che ho aggiunto alcuni prompt testuali complessi come &quot;devi individuare le case vicino alle strade&quot;. In alcuni casi i risultati sono migliori di altri e questo può cambiare grazie a LLM più avanzati o con un numero maggiore di parametri.</p>
  <details>
    <summary>Clicca qui per mostrare un esempio di payload usato come request</summary>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "bbox"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">        "ne"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.173968917056655</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">10.082219839096071</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        },</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">        "sw"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.16651671595163</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">10.066105127334597</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "string_prompt"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"You are a skilled gis analyst with a lot of expertise in photogrammetry, remote sensing and geomorphology field. You need to identify..."</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "zoom"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">17</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "source_type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Esri.WorldImagery"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
</div><p>Si noti in particolare il tag &quot;source_type&quot;, valorizzabile come uno dei tile providers elencati in <a href="https://github.com/leaflet-extras/leaflet-providers" target="_blank" rel="noreferrer">leaflet-extras/leaflet-providers</a>.
Gli usi più ovvi (con i migliori risultati, probabilmente) sono legati all'utilizzo di un tile providers di fotogrammetria satellitare come</p>
<ul>
<li><a href="https://leaflet-extras.github.io/leaflet-providers/preview/#filter=SwissFederalGeoportal.SWISSIMAGE" target="_blank" rel="noreferrer">SwissFederalGeoportal.SWISSIMAGE</a></li>
<li><a href="https://leaflet-extras.github.io/leaflet-providers/preview/#filter=GeoportailFrance.orthos" target="_blank" rel="noreferrer">GeoportailFrance orthos</a></li>
<li><a href="https://leaflet-extras.github.io/leaflet-providers/preview/#filter=Esri.WorldImagery" target="_blank" rel="noreferrer">Esri.WorldImagery</a></li>
<li><a href="https://leaflet-extras.github.io/leaflet-providers/preview/#filter=Stadia.AlidadeSatellite" target="_blank" rel="noreferrer">Stadia.AlidadeSatellite</a></li>
</ul>
<p>Si tenga presente che alcuni dei tile providers di cui sopra sono servizi commerciali e/o prevedono restrizioni all'utilizzo (ad esempio registrazione, accettazione delle condizioni di utilizzo, ecc.).</p>
  </details>
<h2 id="durata-dei-task-di-segmentazione" tabindex="-1">Durata dei task di segmentazione <a class="header-anchor" href="#durata-dei-task-di-segmentazione" aria-label="Permalink to &quot;Durata dei task di segmentazione&quot;"></a></h2>
<p>Al momento, un prompt che richieda anche la spiegazione di quanto identificato nell'immagine rallenta notevolmente l'analisi. Lo stesso prompt d'analisi eseguito sulla stessa immagine però senza richieste di spiegazione viene elaborato molto più velocemente. I test contenenti richieste di spiegazioni vengono eseguiti in più di 60 secondi mentre senza la durata è intorno o inferiore a 4 secondi, utilizzando il profilo hardware HuggingFace &quot;Nvidia T4 Small&quot; con 4 vCPU, 15 GB RAM e 16 GB VRAM.</p>
<h2 id="architettura-software" tabindex="-1">Architettura software <a class="header-anchor" href="#architettura-software" aria-label="Permalink to &quot;Architettura software&quot;"></a></h2>
<p>Dal punto di vista tecnico e architetturale, la <a href="https://huggingface.co/spaces/aletrn/samgis-lisa-on-cuda" target="_blank" rel="noreferrer">demo</a> consiste di un frontend simile a quello sulla demo di <a href="https://huggingface.co/spaces/aletrn/samgis" target="_blank" rel="noreferrer">SamGIS</a>. Niente barra degli strumenti per disegnare, sostituita dalla casella di testo per le richieste in linguaggio naturale. Il backend utilizza un'API basata su FastAPI e che invoca una funzione ad hoc basata su LISA.</p>
<p>Ho dovuto mettere in pausa la demo a causa del costo della GPU, ma sto richiedendo l'uso di una GPU gratuita da HuggingFace. Non esitate a contattarmi su LinkedIn per una dimostrazione dal vivo, chiedere maggiori informazioni o ulteriori chiarimenti.</p>
<p>Al momento è disponibile anche una <a href="https://huggingface.co/spaces/aletrn/samgis-lisa-on-zero/" target="_blank" rel="noreferrer">demo basata su hardware ZeroGPU</a>: è un po' lenta rispetto alla <a href="https://huggingface.co/spaces/aletrn/samgis-lisa-on-cuda" target="_blank" rel="noreferrer">demo cuda</a> classica, ma è gratuita (nel caso io continui a pagare la quota per l'<a href="https://huggingface.co/pricing#pro" target="_blank" rel="noreferrer">abbonamento PRO HuggingFace</a>). Si noti che lo spazio ZeroGPU potrebbe essere in pausa per inattività e, nel caso, servirà attendere per il suo completo caricamento.</p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[LISA adapted to SamGIS]]></title>
            <link>/projects/lisa-adapted-for-samgis</link>
            <guid isPermaLink="false">/projects/lisa-adapted-for-samgis</guid>
            <pubDate>Mon, 08 Apr 2024 23:24:00 GMT</pubDate>
            <description><![CDATA[Explore how LIS (Reasoning Segmentation via Large Language Model) has been adapted for integration with SamGIS]]></description>
            <content:encoded><![CDATA[<h1 id="lisa-adapted-to-samgis" tabindex="-1">LISA adapted to SamGIS <a class="header-anchor" href="#lisa-adapted-to-samgis" aria-label="Permalink to &quot;LISA adapted to SamGIS&quot;"></a></h1>
<p>Image segmentation is a crucial task in computer vision, where the goal is to extract the <a href="https://www.ibm.com/topics/instance-segmentation" target="_blank" rel="noreferrer">instance segmentation mask</a> for a desired object within the image. I've already worked on a project, <a href="/projects/samgis-segment-anything-applied-to-GIS.html">SamGIS</a>, that focuses on this particular application of computer vision. A logical progression now would be incorporating the ability to recognize objects through text prompts. This apparently simple activity is actually different compared to what <a href="https://segment-anything.com/" target="_blank" rel="noreferrer">Segment Anything</a> (the ML backend used by SamGIS) does. In fact &quot;SAM&quot; does not outputs descriptions nor categorizations for its input images. Starting from a written prompt at the contrary requires understanding which classes of objects exist in the image under analysis. A <a href="https://arxiv.org/abs/2305.11175" target="_blank" rel="noreferrer">visual language model</a> (or VLM) that performs well for this task is <a href="https://github.com/dvlab-research/LISA" target="_blank" rel="noreferrer">LISA</a>. LISA's authors built their work on top of <a href="https://segment-anything.com/" target="_blank" rel="noreferrer">Segment Anything</a> and <a href="https://llava-vl.github.io/" target="_blank" rel="noreferrer">Llava</a>, a large language model with multimodal capabilities (it can process both text prompts and images). By leveraging LISA's &quot;reasoned segmentation&quot; abilities, SamGIS can now conduct &quot;zero-shot&quot; analyses, meaning it can operate without specific or specialistic prior training in geological, geomorphological, or photogrammetric fields.</p>
<h2 id="some-input-text-prompts-with-their-geojson-outputs" tabindex="-1">Some input text prompts with their geojson outputs <a class="header-anchor" href="#some-input-text-prompts-with-their-geojson-outputs" aria-label="Permalink to &quot;Some input text prompts with their geojson outputs&quot;"></a></h2>
<div>
  <MapWithPolygonComponent
    :center="[46.17, 10.075]"
    currentInputOutputJson="houses"
    imageOverlayUrl="/projects/teglio-somassa.jpg"
    :imageBounds="[[46.173968917056655, 10.082219839096071], [46.16651671595163, 10.066105127334597]]"
    prefix="projects/teglio-somassa"
    :zoom=15
  />
</div>
<p>I exported the image in overlay from Esri.WorldImagery tiles provider.</p>
<p>Note that I added some complex text prompts like &quot;you need to segment the houses near roads&quot;. In some cases the results are better than others and this can change thanks to more advanced LLMs or with a greater number of parameters.</p>
<details>
  <summary>Click here to show an example payload request</summary>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "bbox"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">        "ne"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.173968917056655</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">10.082219839096071</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        },</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">        "sw"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.16651671595163</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">            "lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">10.066105127334597</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "string_prompt"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"You are a skilled gis analyst with a lot of expertise in photogrammetry, remote sensing and geomorphology field. You need to identify..."</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "zoom"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">17</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "source_type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Esri.WorldImagery"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
</div><p>Note in particular the <code>source_type</code> tag: this can take as values the identifying string of the tile providers listed in <a href="https://github.com/leaflet-extras/leaflet-providers" target="_blank" rel="noreferrer">leaflet-extras/leaflet-providers</a>.
The most obvious uses (and the best results, probably) are using a satellite tiles provider like</p>
<ul>
<li><a href="https://leaflet-extras.github.io/leaflet-providers/preview/#filter=SwissFederalGeoportal.SWISSIMAGE" target="_blank" rel="noreferrer">SwissFederalGeoportal.SWISSIMAGE</a></li>
<li><a href="https://leaflet-extras.github.io/leaflet-providers/preview/#filter=GeoportailFrance.orthos" target="_blank" rel="noreferrer">GeoportailFrance orthos</a></li>
<li><a href="https://leaflet-extras.github.io/leaflet-providers/preview/#filter=Esri.WorldImagery" target="_blank" rel="noreferrer">Esri.WorldImagery</a></li>
<li><a href="https://leaflet-extras.github.io/leaflet-providers/preview/#filter=Stadia.AlidadeSatellite" target="_blank" rel="noreferrer">Stadia.AlidadeSatellite</a></li>
</ul>
<p>Note that some of the tile providers above are commercial services and/or have special requirements to use (e.g. registration, abide by the terms of service, etc).</p>
</details>
<h2 id="duration-of-segmentation-tasks" tabindex="-1">Duration of segmentation tasks <a class="header-anchor" href="#duration-of-segmentation-tasks" aria-label="Permalink to &quot;Duration of segmentation tasks&quot;"></a></h2>
<p>At the moment, a prompt that also requires an explanation about the segmentation task slows down greatly the analysis. The same prompt on the same image without &quot;descriptive&quot; or &quot;explanatory&quot; questions instead finish much faster. Tests with explanatory text perform in more than 60 seconds while without duration is between 3 and 8 seconds, using the HuggingFace hardware profile &quot;Nvidia T4 Small&quot; with 4 vCPU, 15 GB RAM and 16 GB VRAM.</p>
<h2 id="software-architecture" tabindex="-1">Software architecture <a class="header-anchor" href="#software-architecture" aria-label="Permalink to &quot;Software architecture&quot;"></a></h2>
<p>Technically and architecturally, the <a href="https://huggingface.co/spaces/aletrn/samgis-lisa-on-cuda" target="_blank" rel="noreferrer">demo</a> consists of a frontend page like <a href="https://huggingface.co/spaces/aletrn/samgis" target="_blank" rel="noreferrer">SamGIS</a> demo. Instead of the drawing tool bar there is a text prompt for natural language requests with some selectable examples displayed at the top of the page. The backend utilizes a FastAPI-based API that calls a custom LISA function wrapper.</p>
<p>Unfortunately I have to pause my demo due to GPU cost, but I am requesting the use of a free GPU from HuggingFace. Please feel free to reach out to me on LinkedIn for a live demonstration, ask for more information or further clarifications.</p>
<p>Right now there is also a <a href="https://huggingface.co/spaces/aletrn/samgis-lisa-on-zero/" target="_blank" rel="noreferrer">demo hardware-based ZeroGPU</a>: it's a little bit slow compared with the regular <a href="https://huggingface.co/spaces/aletrn/samgis-lisa-on-cuda" target="_blank" rel="noreferrer">cuda demo</a>, but it's free to use (if I keep to pay the fee for <a href="https://huggingface.co/pricing#pro" target="_blank" rel="noreferrer">PRO HuggingFace subscription</a>). Please note that the ZeroGPU space may be paused due to inactivity and you will need to wait for it to fully load.</p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Sci nordico a San Giuseppe]]></title>
            <link>/it/photo-galleries/san-giuseppe-20240128</link>
            <guid isPermaLink="false">/it/photo-galleries/san-giuseppe-20240128</guid>
            <pubDate>Sun, 28 Jan 2024 11:02:00 GMT</pubDate>
            <description><![CDATA[La pista da sci nordico immersa nei boschi di San Giuseppe, in Val Malenco]]></description>
            <content:encoded><![CDATA[<h1 id="san-giuseppe-28-gennaio-2024" tabindex="-1">San Giuseppe, 28 Gennaio 2024 <a class="header-anchor" href="#san-giuseppe-28-gennaio-2024" aria-label="Permalink to &quot;San Giuseppe, 28 Gennaio 2024&quot;"></a></h1>
<h2 id="sci-nordico-a-san-giuseppe" tabindex="-1">Sci nordico a San Giuseppe <a class="header-anchor" href="#sci-nordico-a-san-giuseppe" aria-label="Permalink to &quot;Sci nordico a San Giuseppe&quot;"></a></h2>
<div id="map-container">
    <MapWithPolyline
      :center='[46.305, 9.827]'
      location="San Giuseppe"
      :mapName="mapName"
      :pathUrl="`/paths/${mapName}.json`"
      :photosUrl="`/photos/${mapName}.json`"
      zoom=16
    />
</div>
<h2 id="qualche-foto-di-san-giuseppe" tabindex="-1">Qualche foto di San Giuseppe <a class="header-anchor" href="#qualche-foto-di-san-giuseppe" aria-label="Permalink to &quot;Qualche foto di San Giuseppe&quot;"></a></h2>
<p>Scatti effettuati il 28 Gennaio 2024.</p>
<div id="image-gallery-container">
    <SimpleGallery
      :galleryID="mapName"
      :imagesData="imagesData"
    />
</div>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Nordic skiing in San Giuseppe]]></title>
            <link>/photo-galleries/san-giuseppe-20240128</link>
            <guid isPermaLink="false">/photo-galleries/san-giuseppe-20240128</guid>
            <pubDate>Sun, 28 Jan 2024 11:02:00 GMT</pubDate>
            <description><![CDATA[The Nordic ski slope immersed in the woods of San Giuseppe, in Val Malenco]]></description>
            <content:encoded><![CDATA[<h1 id="san-giuseppe-28-january-2024" tabindex="-1">San Giuseppe, 28 January 2024 <a class="header-anchor" href="#san-giuseppe-28-january-2024" aria-label="Permalink to &quot;San Giuseppe, 28 January 2024&quot;"></a></h1>
<h2 id="nordic-skiing-in-san-giuseppe" tabindex="-1">Nordic skiing in San Giuseppe <a class="header-anchor" href="#nordic-skiing-in-san-giuseppe" aria-label="Permalink to &quot;Nordic skiing in San Giuseppe&quot;"></a></h2>
<div id="map-container">
    <MapWithPolyline
      :center='[46.305, 9.827]'
      location="San Giuseppe"
      :mapName="mapName"
      :pathUrl="`/paths/${mapName}.json`"
      :photosUrl="`/photos/${mapName}.json`"
      zoom=16
    />
</div>
<h2 id="let-s-take-some-photos-of-san-giuseppe" tabindex="-1">Let's take some photos of San Giuseppe <a class="header-anchor" href="#let-s-take-some-photos-of-san-giuseppe" aria-label="Permalink to &quot;Let's take some photos of San Giuseppe&quot;"></a></h2>
<p>Photos taken on 28 January 2024.</p>
<div id="image-gallery-container">
    <SimpleGallery
      :galleryID="mapName"
      :imagesData="imagesData"
    />
</div>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[How to learn machine learning]]></title>
            <link>/blog/how-to-learn-machine-learning</link>
            <guid isPermaLink="false">/blog/how-to-learn-machine-learning</guid>
            <pubDate>Sun, 14 Jan 2024 15:50:56 GMT</pubDate>
            <description><![CDATA[Explore resources, methodologies, and best practices to begin your machine learning training journey with this little guide]]></description>
            <content:encoded><![CDATA[<h1 id="some-notes-about-machine-learning" tabindex="-1">Some notes about machine learning <a class="header-anchor" href="#some-notes-about-machine-learning" aria-label="Permalink to &quot;Some notes about machine learning&quot;"></a></h1>
<p>It's a way in which a machine is able to perform tasks without explicit programming. Theoretical bases have existed since the 1960s, but significant results are possible thanks to dedicated hardware, algorithms capable of exploiting this power and well structured datasets.</p>
<h2 id="a-classification-example" tabindex="-1">A classification example <a class="header-anchor" href="#a-classification-example" aria-label="Permalink to &quot;A classification example&quot;"></a></h2>
<p>A software programmed explicitly to perform image classification would need to identify explicitly the characteristics of the object. This can work if all objects are reasonably simple and similar. But what if the some features of the same object category have an high variance?</p>
<h3 id="an-approach-without-explicit-instructions" tabindex="-1">An approach without explicit instructions <a class="header-anchor" href="#an-approach-without-explicit-instructions" aria-label="Permalink to &quot;An approach without explicit instructions&quot;"></a></h3>
<p>We could submit a suitably labeled dataset of image data to the software (i.e. the machine learning model) by performing an appropriate number of training-and-testing cycles on it. Another example could use linear regression to predict prices of houses by their sizes. The common ground here is study the data to form an hypothesis. Adding more data probably will expose errors within the hypothesis, then we can measure the error gap and form an updated hypothesis with a minor error gap.</p>
<p>The machine has to analyze the dataset using some assumptions, e.g. a linear variable that can be represented as a straight line on a Cartesian plane. The machine goal is to test every possibile result to extract the one with the lowest error rate. It's simple with a linear variable, much more complex with some modern machine learning models that have billions of variables, but the principle is the same: the machine start from a random point (or parameters set) and move towards the lowest error rate, cycle after cycle of train and test.</p>
<p>Every train-and-test step give a measure (an &quot;error function&quot; or &quot;loss function&quot;) and the repetition of these steps is the &quot;gradient descent&quot; towards the solution.</p>
<p>There are various ways to improve results such as <a href="https://en.wikipedia.org/wiki/Backpropagation" target="_blank" rel="noreferrer">backpropagation</a> to fine-tune the data parameters and <a href="https://machinelearningmastery.com/dropout-for-regularizing-deep-neural-networks/" target="_blank" rel="noreferrer">neural node dropouts</a> to reduce overfitting (when the ML model is no longer can generalize well on real world data).</p>
<h2 id="some-machine-learning-categories" tabindex="-1">Some machine learning categories <a class="header-anchor" href="#some-machine-learning-categories" aria-label="Permalink to &quot;Some machine learning categories&quot;"></a></h2>
<ul>
<li><a href="https://cloud.google.com/discover/what-is-supervised-learning" target="_blank" rel="noreferrer">supervised learning</a>: image classification, speech/text recognition, fraud detection</li>
<li><a href="https://cloud.google.com/discover/what-is-unsupervised-learning" target="_blank" rel="noreferrer">unsupervised learning</a>: grouping data into clusters or identifying anomalies</li>
<li><a href="https://www.mathworks.com/discovery/reinforcement-learning.html" target="_blank" rel="noreferrer">reinforcement learning</a>: the software finds the best solution for a given problem</li>
</ul>
<h2 id="sources-to-learn-more" tabindex="-1">Sources to learn more <a class="header-anchor" href="#sources-to-learn-more" aria-label="Permalink to &quot;Sources to learn more&quot;"></a></h2>
<ul>
<li><a href="https://cloud.google.com/products/ai/ml-comic-1" target="_blank" rel="noreferrer">AI and machine learning products</a></li>
<li><a href="https://www.manning.com/books/deep-learning-with-python-second-edition" target="_blank" rel="noreferrer">Deep Learning with Python, Second Edition</a>, by François Chollet</li>
<li><a href="https://www.ibm.com/topics/machine-learning" target="_blank" rel="noreferrer">IBM: What is machine learning?</a></li>
<li><a href="https://www.agendadigitale.eu/cultura-digitale/machine-learning-cose-e-come-funziona/" target="_blank" rel="noreferrer">Machine learning, cos’è e come funziona questa branca dell’intelligenza artificiale</a></li>
<li><a href="https://www.understandingai.org/p/large-language-models-explained-with" target="_blank" rel="noreferrer">Large language models, explained with a minimum of math and jargon</a></li>
</ul>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Appunti sul machine learning]]></title>
            <link>/it/blog/how-to-learn-machine-learning</link>
            <guid isPermaLink="false">/it/blog/how-to-learn-machine-learning</guid>
            <pubDate>Sun, 14 Jan 2024 15:50:56 GMT</pubDate>
            <description><![CDATA[Esplora risorse, metodologie e pratiche consigliate per iniziare il tuo percorso di formazione sul machine learning con questa piccola guida]]></description>
            <content:encoded><![CDATA[<h1 id="alcune-note-sull-apprendimento-automatico" tabindex="-1">Alcune note sull'apprendimento automatico <a class="header-anchor" href="#alcune-note-sull-apprendimento-automatico" aria-label="Permalink to &quot;Alcune note sull'apprendimento automatico&quot;"></a></h1>
<p>È un sistema in cui una macchina può eseguire compiti senza programmazione esplicita. Le basi teoriche esistono fin dagli anni '60, ma risultati significativi sono possibili (dal 2013 in poi circa) grazie ad hardware dedicato, algoritmi in grado di sfruttarne la potenza e dataset di dati ben strutturati e catalogati.</p>
<h2 id="un-esempio-di-classificazione" tabindex="-1">Un esempio di classificazione <a class="header-anchor" href="#un-esempio-di-classificazione" aria-label="Permalink to &quot;Un esempio di classificazione&quot;"></a></h2>
<p>Un software programmato esplicitamente per eseguire la classificazione delle immagini dovrebbe identificare esplicitamente le caratteristiche dell'oggetto. Questo può funzionare se tutti gli oggetti in questione sono ragionevolmente semplici e simili. Ma cosa succede se alcune caratteristiche della stessa categoria di oggetti presentano una varianza elevata?</p>
<h3 id="un-approccio-senza-istruzioni-esplicite" tabindex="-1">Un approccio senza istruzioni esplicite <a class="header-anchor" href="#un-approccio-senza-istruzioni-esplicite" aria-label="Permalink to &quot;Un approccio senza istruzioni esplicite&quot;"></a></h3>
<p>Potremmo sottoporre al software (ovvero il modello di machine learning) un dataset di dati di immagine opportunamente etichettato eseguendo su di esso un numero opportuno di cicli di training e test. Un altro esempio potrebbe utilizzare la regressione lineare per prevedere i prezzi delle case in base alle loro dimensioni. Le caratteristiche comuni di questi due esempi sono lo studio dei dati per formare un’ipotesi. L'aggiunta di più dati probabilmente esporrà errori all'interno dell'ipotesi, quindi potremo misurare il divario di errore e formare un'ipotesi aggiornata con un divario di errore minore.</p>
<p>La macchina deve analizzare il set di dati utilizzando alcune ipotesi, ad es. una variabile lineare rappresentabile come una retta su un piano cartesiano. L'obiettivo della macchina è testare ogni possibile risultato per estrarre quello con il tasso di errore più basso. È semplice con una variabile lineare, molto più complesso con alcuni moderni modelli di machine learning che hanno miliardi di variabili, ma il principio è lo stesso: la macchina parte da un punto (o set di parametri) casuale e si muove verso il tasso di errore più basso, ciclo dopo ciclo di treno e prova.</p>
<p>Ogni passaggio di training e test fornisce una misura (una &quot;funzione di errore&quot; o &quot;funzione di perdita&quot;) e la ripetizione di questi passaggi è il &quot;gradiente di discesa&quot; verso la soluzione.</p>
<p>Esistono vari modi per migliorare i risultati come la <a href="https://en.wikipedia.org/wiki/Backpropagation" target="_blank" rel="noreferrer">backpropagation</a> per ottimizzare i parametri dei dati e [gli abbandoni dei nodi neurali](<a href="https://machinelearningmastery.com/dropout" target="_blank" rel="noreferrer">https://machinelearningmastery.com/dropout</a> -for-regularizing-deep-neural-networks/) per ridurre l'overfitting (quando il modello ML non è più in grado di generalizzare bene sui dati del mondo reale).</p>
<h2 id="alcune-categorie-di-machine-learning" tabindex="-1">Alcune categorie di machine learning <a class="header-anchor" href="#alcune-categorie-di-machine-learning" aria-label="Permalink to &quot;Alcune categorie di machine learning&quot;"></a></h2>
<ul>
<li><a href="https://cloud.google.com/discover/what-is-supervised-learning" target="_blank" rel="noreferrer">apprendimento supervisionato</a>: classificazione di immagini, riconoscimento del testo e vocale, rilevamento di frodi</li>
<li><a href="https://cloud.google.com/discover/what-is-unsupervised-learning" target="_blank" rel="noreferrer">apprendimento non supervisionato</a>: raggruppamento dei dati in cluster o individuazione di anomalie</li>
<li><a href="https://www.mathworks.com/discovery/reinforcement-learning.html" target="_blank" rel="noreferrer">apprendimento per rinforzo</a>: il software trova la soluzione migliore per un dato problema</li>
</ul>
<h2 id="risorse-per-l-approfondimento" tabindex="-1">Risorse per l'approfondimento <a class="header-anchor" href="#risorse-per-l-approfondimento" aria-label="Permalink to &quot;Risorse per l'approfondimento&quot;"></a></h2>
<ul>
<li><a href="https://cloud.google.com/products/ai/ml-comic-1" target="_blank" rel="noreferrer">AI and machine learning products</a></li>
<li><a href="https://www.manning.com/books/deep-learning-with-python-second-edition" target="_blank" rel="noreferrer">Deep Learning with Python, Second Edition</a>, di François Chollet</li>
<li><a href="https://www.ibm.com/it-it/topics/machine-learning" target="_blank" rel="noreferrer">IBM: cos'è il machine learning?</a></li>
<li><a href="https://www.agendadigitale.eu/cultura-digitale/machine-learning-cose-e-come-funziona/" target="_blank" rel="noreferrer">Machine learning, cos’è e come funziona questa branca dell’intelligenza artificiale</a></li>
<li><a href="https://www.understandingai.org/p/large-language-models-explained-with" target="_blank" rel="noreferrer">Large language models, explained with a minimum of math and jargon</a></li>
</ul>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Using a Single Page Application (SPA) on a Hugging Face Space]]></title>
            <link>/blog/spa-frontend-huggingface-space</link>
            <guid isPermaLink="false">/blog/spa-frontend-huggingface-space</guid>
            <pubDate>Fri, 29 Dec 2023 17:12:00 GMT</pubDate>
            <description><![CDATA[How to develop the frontend of a Huggingface Space using a single page application (SPA)]]></description>
            <content:encoded><![CDATA[<h1 id="using-a-single-page-application-spa-on-a-hugging-face-space" tabindex="-1">Using a Single Page Application (SPA) on a Hugging Face Space <a class="header-anchor" href="#using-a-single-page-application-spa-on-a-hugging-face-space" aria-label="Permalink to &quot;Using a Single Page Application (SPA) on a Hugging Face Space&quot;"></a></h1>
<p>When I started working on this <a href="https://huggingface.co/spaces/aletrn/samgis" target="_blank" rel="noreferrer">Hugging Face demo version</a> of <a href="/projects/samgis-segment-anything-applied-to-GIS.html">SamGIS</a> I needed also to deploy the static fronted.
Since the Hugging Face backend uses <a href="https://fastapi.tiangolo.com" target="_blank" rel="noreferrer">Fastapi</a> I changed the way I use <code>StaticFiles()</code> this way:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">app.mount(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">"/"</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">,</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> StaticFiles</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(directory</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"static/dist",</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> html</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">True</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">,</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> name="static"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span></code></pre>
</div><p>Note that the <code>static</code> folder contains both the <code>dist</code> and the <code>node_modules</code> folder:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">static/</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">├──</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> dist</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">│</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">   ├──</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> assets</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">│</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">   │</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">   └──</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> index-FDtpfbXx.js</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">│</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">   ├──</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> index.html</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">│</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">   ├──</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> output.css</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">│</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">   └──</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> ...</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> png,</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> svg,</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> etc</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">└──</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> node_modules</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    └──</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> ...</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">9</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> directories,</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 22</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> files</span></span></code></pre>
</div><p>Remember that to use a tailwind css it's necessary to add a dedicated build within the dockerfile like this:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D"># dockerfile</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">RUN</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> --mount=type=cache,id=pnpm,target=/pnpm/store</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [ </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">DEPENDENCY_GROUP</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}"</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "fastapi"</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> ]</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">then</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> \</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    pnpm</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> build</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">fi</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">RUN</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> --mount=type=cache,id=pnpm,target=/pnpm/store</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [ </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">DEPENDENCY_GROUP</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}"</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "fastapi"</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> ]</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">then</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> \</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    pnpm</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> tailwindcss</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -i</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> /appnode/src/input.css</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -o</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> /appnode/dist/output.css</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">fi</span></span></code></pre>
</div><p>That's the way you do it an <a href="https://xkcd.com/1425/" target="_blank" rel="noreferrer">easy task</a>!</p>
<p><img src="https://imgs.xkcd.com/comics/tasks.png" alt="image-instance-segmentation-task"></p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Come usare una Single Page Application (SPA) su Hugging Face Space]]></title>
            <link>/it/blog/spa-frontend-huggingface-space</link>
            <guid isPermaLink="false">/it/blog/spa-frontend-huggingface-space</guid>
            <pubDate>Fri, 29 Dec 2023 17:12:00 GMT</pubDate>
            <description><![CDATA[Come realizzare il frontend di un Huggingface Space usando una single page application (SPA)]]></description>
            <content:encoded><![CDATA[<h1 id="come-usare-una-single-page-application-spa-su-hugging-face-space" tabindex="-1">Come usare una Single Page Application (SPA) su Hugging Face Space <a class="header-anchor" href="#come-usare-una-single-page-application-spa-su-hugging-face-space" aria-label="Permalink to &quot;Come usare una Single Page Application (SPA) su Hugging Face Space&quot;"></a></h1>
<p>Quando ho iniziato a lavorare su questa <a href="https://huggingface.co/spaces/aletrn/samgis" target="_blank" rel="noreferrer">versione demo di Hugging Face</a>
di <a href="/projects/samgis-segment-anything-applied-to-GIS.html">SamGIS</a> avevo bisogno anche per implementare il frontend statico.
Dato che il backend di Hugging Face utilizza <a href="https://fastapi.tiangolo.com" target="_blank" rel="noreferrer">Fastapi</a> modificato l'utilizzo di <code>StaticFiles()</code> così:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">app.mount(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">"/"</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">,</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> StaticFiles</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(directory</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"static/dist",</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> html</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">True</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">,</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> name="static"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span></code></pre>
</div><p>Si tenga presente che la cartella <code>static</code> contiene sia la cartella <code>dist</code> che la cartella <code>node_modules</code>:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">static/</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">├──</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> dist</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">│</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">   ├──</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> assets</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">│</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">   │</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">   └──</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> index-FDtpfbXx.js</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">│</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">   ├──</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> index.html</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">│</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">   ├──</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> output.css</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">│</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">   └──</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> ...</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> png,</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> svg,</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> etc</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">└──</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> node_modules</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    └──</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> ...</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">9</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> directories,</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 22</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> files</span></span></code></pre>
</div><p>Per utilizzare un css prodotto da tailwind serve aggiungere uno step di build dedicato all'interno del dockerfile:</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">#dockerfile</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">RUN</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> --mount=type=cache,id=pnpm,target=/pnpm/store</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [ </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">DEPENDENCY_GROUP</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}"</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "fastapi"</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> ]</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">then</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> \</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    pnpm</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> build</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">fi</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">RUN</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> --mount=type=cache,id=pnpm,target=/pnpm/store</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [ </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">DEPENDENCY_GROUP</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}"</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "fastapi"</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> ]</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">then</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> \</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    pnpm</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> tailwindcss</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -i</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> /appnode/src/input.css</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -o</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> /appnode/dist/output.css</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">fi</span></span></code></pre>
</div><p>Questo è il modo in cui si fa in un <a href="https://xkcd.com/1425/" target="_blank" rel="noreferrer">lavoretto semplice</a>!</p>
<p><img src="https://imgs.xkcd.com/comics/tasks.png" alt="attività-segmentazione-istanza-immagine"></p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[SamGIS - Segment Anything applied to GIS]]></title>
            <link>/blog/samgis-segment-anything-applied-to-GIS</link>
            <guid isPermaLink="false">/blog/samgis-segment-anything-applied-to-GIS</guid>
            <pubDate>Mon, 23 Oct 2023 13:10:00 GMT</pubDate>
            <description><![CDATA[Discover the groundbreaking capabilities of Segment Anything thanks to SamGIS]]></description>
            <content:encoded><![CDATA[<h1 id="samgis-segment-anything-applied-to-geographic-information-systems-gis" tabindex="-1">SamGIS - Segment Anything applied to Geographic Information Systems (GIS) <a class="header-anchor" href="#samgis-segment-anything-applied-to-geographic-information-systems-gis" aria-label="Permalink to &quot;SamGIS - Segment Anything applied to Geographic Information Systems (GIS)&quot;"></a></h1>
<p>This project, composed by a <a href="https://en.wikipedia.org/wiki/Single-page_application" target="_blank" rel="noreferrer">SPA</a> frontend and by a backend (<a href="https://github.com/trincadev/samgis-be/" target="_blank" rel="noreferrer">samgis-be</a>), is an attempt to perform machine learning image segmentation on geo-spatial data even without the use of dedicated graphics cards. I recently added the ability to start from <a href="https://trinca.tornidor.com/projects/lisa-adapted-for-samgis" target="_blank" rel="noreferrer">natural language text prompts</a>.</p>
<p>I wrote the backend adapting <a href="https://github.com/vietanhdev/samexporter" target="_blank" rel="noreferrer">SAM Exporter</a> with the aim to use the machine learning <a href="https://segment-anything.com/" target="_blank" rel="noreferrer">segment anything</a> project for the purpose to improve polygons recognition in GIS web applications.</p>
<p>Starting from version 1.5.1 the backend integrates changes borrowed from <a href="https://github.com/AndreyGermanov/sam_onnx_full_export" target="_blank" rel="noreferrer">sam_onnx_full_export</a>, to support OnnxRuntime 1.17.x and later versions. Please note that on MacOS directly running the project from the command line suffers from <a href="https://github.com/microsoft/onnxruntime/issues/14455" target="_blank" rel="noreferrer">memory leaks</a>, making inference operations slower than normal. It's best therefore running the project inside a docker container, unless in case of development or debugging activities.</p>
<p>These are the project resource links:</p>
<ul>
<li>a public <a href="https://huggingface.co/spaces/aletrn/samgis" target="_blank" rel="noreferrer">HuggingFace space demo</a></li>
<li>the <a href="https://docs.ml-trinca.tornidor.com" target="_blank" rel="noreferrer">documentation and AWS lambda OpenAPI specs</a></li>
</ul>
<p>Note that the HuggingFace space demo has its static frontend files under the /static folder.</p>
<p>Ask me for a user via <a href="https://www.linkedin.com/in/trincatornidor/" target="_blank" rel="noreferrer">linkedin</a> if you would like to test the authenticated demo or an explanation about the code.</p>
<h3 id="lisa-and-samgis" tabindex="-1">LISA and SamGIS <a class="header-anchor" href="#lisa-and-samgis" aria-label="Permalink to &quot;LISA and SamGIS&quot;"></a></h3>
<p>I also added support for image segmentation via natural language text prompts: here are the details about the <a href="/projects/lisa-adapted-for-samgis.html">SamGIS integration with LISA</a>.</p>
<h2 id="samgis-also-replaces-surferdtm" tabindex="-1">SamGIS also replaces SurferDTM <a class="header-anchor" href="#samgis-also-replaces-surferdtm" aria-label="Permalink to &quot;SamGIS also replaces SurferDTM&quot;"></a></h2>
<p><a href="/projects/surferdtm-docs.html">SurferDTM</a> was an attempt to find <a href="https://volcano.oregonstate.edu/stratovolcanoes" target="_blank" rel="noreferrer">stratovolcanoes</a> within digital terrain models (DEM/DTM). As a new feature SamGIS now can now use also input DEM images (like the first image in this gallery):</p>
<div id="image-gallery-container-dem">
    <GalleryNoMap
      :galleryID="galleryNameDEM"
      :imagesData="imagesDataDEM"
    />
</div>
<p>Note that the &quot;terrarium&quot; source provider data encode the image as RGB, so the backend processes it to obtain the elevation data (like the second image in this gallery) using this formula (from the <a href="https://www.mapzen.com/blog/terrain-tile-service/">map tile provider page</a>):</p>
<blockquote>
<p>(red * 256) + green + (blue / 256) - 32768</p>
</blockquote>
<p>I also added a custom elaboration step to create an intermediate RGB image composed by channels:</p>
<ul>
<li>RED - normalized DEM image</li>
<li>GREEN - normalized 2d composed image by
<ul>
<li>normalized DEM image</li>
<li>slope</li>
<li>curvature</li>
</ul>
</li>
<li>BLUE - curvature</li>
</ul>
<div id="image-gallery-container-rgb2">
    <GalleryNoMap
      :galleryID="galleryNameRGB2"
      :imagesData="imagesDataRGB2"
    />
</div>
<p>Segment Anything will finally do the inference on this rgb image.</p>
<details>
<summary><i>Open this element detail to view the json request I use to create the DEM and RGB intermediate images.</i></summary>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">   "bbox"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">         "ne"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.203706648934954</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.401906739170105</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">         "sw"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.12464369105346</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.231103669101747</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}},</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">   "prompt"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">238</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.158760519552146</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.306795638666486</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">250</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.152693546080975</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.289288652508679</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">264</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.16613515509541</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.319840059725284</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">272</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.161377438862836</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.288087192674299</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">285</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.174103408003454</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.308340372739261</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">293</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.17005996124035</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.298042145587583</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">   ],</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">   "zoom"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">13</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">   "source_type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"nextzen.terrarium"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
</div></details>
<h2 id="project-architecture-of-aws-demo" tabindex="-1">Project Architecture of AWS demo <a class="header-anchor" href="#project-architecture-of-aws-demo" aria-label="Permalink to &quot;Project Architecture of AWS demo&quot;"></a></h2>
<div id="image-gallery-container-arch">
    <GalleryNoMap
      :galleryID="galleryNameArch"
      :imagesData="imagesDataArch"
    />
</div>
<ol>
<li>
<p>The user interact with a WebMap (in my example it's a leaflet map that use OpenStreetMap map data).</p>
</li>
<li>
<p>The user send a POST request to an <a href="https://docs.ml-trinca.tornidor.com/openapi" target="_blank" rel="noreferrer">API</a> using</p>
<ol>
<li>the current map extent</li>
<li>the current map zoom</li>
<li>a prompt array of objects; objects could be
<ul>
<li>an &quot;include&quot; marker position (a couple of float - latitude and longitude)</li>
<li>an &quot;exclusionary&quot; marker position (a couple of float - latitude and longitude)</li>
<li>a &quot;include&quot; rectangle (two latitude and longitude positions - north-east and south-west)</li>
</ul>
</li>
</ol>
</li>
<li>
<p>because of CORS constrain it's difficult to directly send requests to remote resources on different domains: to avoid this problems I prepared a simple Cloudflare proxy function that forward the user request to the remote API</p>
</li>
<li>
<p>The API Gateway proxies the request to the serverless backend</p>
</li>
<li>
<p>the serverless Lambda API elaborate the request:</p>
<ol>
<li>it parses the request and in particular translate the input latitude/longitude prompt into a x/y (pixel coordinates) one</li>
<li>it downloads the geo-referenced tiles then merge and crop them</li>
<li>it instantiates a <a href="https://github.com/ChaoningZhang/MobileSAM" target="_blank" rel="noreferrer">MobileSam</a> model instance if not already created using OnnxRuntime as runtime</li>
<li>it prepares the <a href="https://datasciencedojo.com/blog/embeddings-and-llm/#" target="_blank" rel="noreferrer">image embedding</a> and...</li>
<li>...it starts the image inference process</li>
<li>it transforms the recognition masks into a single binary mask then it <a href="https://gisgeography.com/rasterization-vectorization/" target="_blank" rel="noreferrer">vectorizes</a> the recognized binary mask:
<ol>
<li>first it converts the mask into a GeoPandas GeoDataframe using <a href="https://rasterio.readthedocs.io/en/stable/api/rasterio.features.html#rasterio.features.shapes" target="_blank" rel="noreferrer"><code>rasterio.features.shapes</code></a> and <a href="https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoDataFrame.from_features.html" target="_blank" rel="noreferrer"><code>GeoDataFrame.from_features</code></a></li>
<li>then it convert the new Geodataframe into a geojson using <a href="https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoDataFrame.to_json.html" target="_blank" rel="noreferrer"><code>GeoDataFrame.to_json</code></a></li>
</ol>
</li>
</ol>
</li>
<li>
<p>the Cloudflare proxy function parses the response and forward it to the frontend</p>
</li>
<li>
<p>the frontend parse the geojson to load within the WebMap and shows some response info like the <a href="https://xkcd.com/2867/" target="_blank" rel="noreferrer">non-relativistic response duration time</a>:</p>
<p><img src="https://imgs.xkcd.com/comics/datetime.png" alt="datetime-diff"></p>
</li>
</ol>
<p>To avoid abuse the <a href="https://ml-trinca.tornidor.com/prediction-map" target="_blank" rel="noreferrer">prompt input web map that I host on my personal site</a> is under authentication (I use <a href="https://auth0.com" target="_blank" rel="noreferrer">auth0.com</a>).</p>
<h2 id="fastapi-based-demo-architecture" tabindex="-1">Fastapi-based demo architecture <a class="header-anchor" href="#fastapi-based-demo-architecture" aria-label="Permalink to &quot;Fastapi-based demo architecture&quot;"></a></h2>
<p>This project variant works almost as <a href="/projects/samgis-segment-anything-applied-to-GIS.html#project-architecture-of-aws-demo">this</a>. There are these differences:</p>
<ul>
<li><code>opencv-python</code> is not anymore a SamGIS project dependency</li>
<li>the server now saves and re-uses image <em>embeddings</em> to avoid resource waste and speed up the inference process</li>
<li>SPA frontend now is integrated directly within the Fastapi backend</li>
</ul>
<p>Frontend and backend now are on the same domain. Therefore there is no longer use for a proxy (the AWS demo used a <a href="https://developers.cloudflare.com/pages/functions/" target="_blank" rel="noreferrer">Cloudflare Pages function</a>) to make requests to APIs on a different domain.</p>
<p>Additionally, since I can <a href="https://huggingface.co/spaces/aletrn/samgis" target="_blank" rel="noreferrer">host this Fastapi demo</a> for <a href="https://huggingface.co/pricing#hub" target="_blank" rel="noreferrer">free on HuggingFace</a>, I don't need anymore an authentication system.</p>
<h2 id="aws-lambda-a-serverless-solution-some-pros" tabindex="-1">AWS lambda, a serverless solution: some pros... <a class="header-anchor" href="#aws-lambda-a-serverless-solution-some-pros" aria-label="Permalink to &quot;AWS lambda, a serverless solution: some pros...&quot;"></a></h2>
<ul>
<li>It can work also with docker images for complex or big projects (like this)</li>
<li>Almost easy, working out-of-the-box integration with other AWS services and third-parts authentication systems</li>
<li>Pay-as-you-go service</li>
</ul>
<h2 id="and-some-cons" tabindex="-1">...and some cons <a class="header-anchor" href="#and-some-cons" aria-label="Permalink to &quot;...and some cons&quot;"></a></h2>
<ul>
<li>Setup not always easy (I used <a href="https://docs.powertools.aws.dev/lambda/python/latest/" target="_blank" rel="noreferrer">Powertools for AWS Lambda (Python)</a> to improve my logger setup)</li>
<li>The serverless model suggested by AWS <a href="https://offbynone.io/issues/233/" target="_blank" rel="noreferrer">not always it's the best solution</a></li>
<li>Arm graviton EC2 instances that run AWS lambda are greatly limited (<a href="https://github.com/microsoft/onnxruntime/issues/10038" target="_blank" rel="noreferrer">OnnxRuntime at the moment cannot run on Arm AWS lambda and probably will never run</a>).</li>
</ul>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[SamGIS - Segment Anything adattato al GIS]]></title>
            <link>/it/blog/samgis-segment-anything-applied-to-GIS</link>
            <guid isPermaLink="false">/it/blog/samgis-segment-anything-applied-to-GIS</guid>
            <pubDate>Mon, 23 Oct 2023 13:10:00 GMT</pubDate>
            <description><![CDATA[Scopri le funzionalità rivoluzionarie di Segment Anything grazie a SamGIS]]></description>
            <content:encoded><![CDATA[<h1 id="samgis-segment-anything-adattato-ai-sistemi-geografici-informativi-gis" tabindex="-1">SamGIS - Segment Anything adattato ai sistemi geografici informativi (GIS) <a class="header-anchor" href="#samgis-segment-anything-adattato-ai-sistemi-geografici-informativi-gis" aria-label="Permalink to &quot;SamGIS - Segment Anything adattato ai sistemi geografici informativi (GIS)&quot;"></a></h1>
<p>Questo progetto, composto da frontend <a href="https://en.wikipedia.org/wiki/Single-page_application" target="_blank" rel="noreferrer">SPA</a> e da una parte backend (<a href="https://github.com/trincadev/samgis-be/" target="_blank" rel="noreferrer">samgis-be</a>), è un tentativo di effettuare riconoscimento d'immagine basato sul machine learning su immagini derivanti da dati geo-spaziali anche senza l'utilizzo di capacità computazionali particolari. Di recente ho aggiunto la possibilità di partire da <a href="/it/projects/lisa-adapted-for-samgis.html">prompt testuali in linguaggio naturale</a>.</p>
<p>La prima versione del backend era modificata da <a href="https://github.com/vietanhdev/samexporter" target="_blank" rel="noreferrer">SAM Exporter</a> semplificando l'utilizzo di <a href="https://segment-anything.com/" target="_blank" rel="noreferrer">segment anything</a> al fine di migliorare il riconoscimento di poligoni in applicazioni web GIS.</p>
<p>Dalla versione 1.5.1 il backend integra modifiche mutuate da <a href="https://github.com/AndreyGermanov/sam_onnx_full_export" target="_blank" rel="noreferrer">sam_onnx_full_export</a>, in modo da supportare OnnxRuntime 1.17.x e versioni successive. Si noti che su MacOS l'esecuzione diretta da linea di comando soffre di <a href="https://github.com/microsoft/onnxruntime/issues/14455" target="_blank" rel="noreferrer">memory leak</a>, rendendo le operazioni di inferenza più lente di quanto sarebbe normale. Si consiglia quindi l'esecuzione del progetto dentro ad un container docker, a meno di voler fare attività di sviluppo e debug.</p>
<p>Questi sono i link alle risorse del progetto:</p>
<ul>
<li>una <a href="https://huggingface.co/spaces/aletrn/samgis" target="_blank" rel="noreferrer">demo di HuggingFace Space</a> pubblica</li>
<li>la <a href="https://docs.ml-trinca.tornidor.com" target="_blank" rel="noreferrer">documentazione e le specifiche AWS lambda OpenAPI</a></li>
</ul>
<p>Si tenga presente che la demo dello spazio HuggingFace ha i suoi file frontend statici nella cartella /static.</p>
<p>Chiunque desideri una spiegazione dettagliata sul funzionamento di questo progetto oppure voglia testare la demo sotto autenticazione può contattarmi tramite <a href="https://www.linkedin.com/in/trincatornidor/" target="_blank" rel="noreferrer">linkedin</a>.</p>
<h3 id="lisa-e-samgis" tabindex="-1">LISA e SamGIS <a class="header-anchor" href="#lisa-e-samgis" aria-label="Permalink to &quot;LISA e SamGIS&quot;"></a></h3>
<p>Ho anche aggiunto il supporto alla segmentazione d'immagine tramite prompt testuali in linguaggio naturale: qui i dettagli a proposito dell'<a href="/it/projects/lisa-adapted-for-samgis.html">integrazione di SamGIS con LISA</a>.</p>
<h2 id="samgis-rimpiazza-anche-surferdtm" tabindex="-1">SamGIS rimpiazza anche SurferDTM <a class="header-anchor" href="#samgis-rimpiazza-anche-surferdtm" aria-label="Permalink to &quot;SamGIS rimpiazza anche SurferDTM&quot;"></a></h2>
<p><a href="/it/projects/surferdtm-docs.html">SurferDTM</a> è un tentativo di trovare <a href="https://volcano.oregonstate.edu/stratovolcanoes" target="_blank" rel="noreferrer">stratovulcani</a> all'interno di modelli digitali del terreno (DEM/DTM). Come nuova funzionalità, SamGIS ora può utilizzare anche DEM di input (come la prima immagine in questa galleria):</p>
<div id="image-gallery-container-dem">
    <GalleryNoMap
      :galleryID="galleryNameDEM"
      :imagesData="imagesDataDEM"
    />
</div>
<p>Si tenga conto che il provider dei dati &quot;terrarium&quot; codifica le immagini come RGB, quindi il backend le elabora per ottenere le quote di terreno effettive (come nella seconda immagine in questa galleria) utilizzando questa formula (dalla <a href="https:// www.mapzen.com/blog/terrain-tile-service/">pagina del provider, mapzen/nextzen</a>):</p>
<blockquote>
<p>(red * 256) + green + (blue / 256) - 32768</p>
</blockquote>
<p>I also added a custom elaboration step to create an intermediate RGB image composed by channels:</p>
<ul>
<li>ROSSO - immagine DEM normalizzata</li>
<li>VERDE - immagine 2d normalizzata composta dalla risultante di
<ul>
<li>immagine DEM normalizzata</li>
<li>slope</li>
<li>curvatura</li>
</ul>
</li>
<li>BLU - curvatura</li>
</ul>
<div id="image-gallery-container-rgb2">
    <GalleryNoMap
      :galleryID="galleryNameRGB2"
      :imagesData="imagesDataRGB2"
    />
</div>
<p>Quest'immagine RGB infine sarà sottoposta a Segment Anything per l'inferenza.</p>
<details>
<summary><i>Apri questo elemento di dettaglio per visualizzare il json usato per creare le immagini intermedie DEM e RGB.</i></summary>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">   "bbox"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">         "ne"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.203706648934954</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.401906739170105</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">         "sw"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.12464369105346</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.231103669101747</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}},</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">   "prompt"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">238</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.158760519552146</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.306795638666486</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">250</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.152693546080975</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.289288652508679</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">264</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.16613515509541</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.319840059725284</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">272</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.161377438862836</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.288087192674299</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">285</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.174103408003454</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.308340372739261</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">293</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.17005996124035</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.298042145587583</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">   ],</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">   "zoom"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">13</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">   "source_type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"nextzen.terrarium"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
</div></details>
<h2 id="architettura-della-demo-aws" tabindex="-1">Architettura della demo AWS <a class="header-anchor" href="#architettura-della-demo-aws" aria-label="Permalink to &quot;Architettura della demo AWS&quot;"></a></h2>
<div id="contenitore-galleria-immagini">
     <GalleryNoMap
       :galleryID="nomegalleria"
       :imagesData="immaginiData"
     />
</div>
<ol>
<li>
<p>L'utente interagisce con una WebMap (nel mio esempio è una mappa leaflet che utilizza i dati cartografici di OpenStreetMap).</p>
</li>
<li>
<p>L'utente invia una POST ad un'<a href="https://docs.ml-trinca.tornidor.com/openapi" target="_blank" rel="noreferrer">API</a> utilizzando</p>
<ol>
<li>le coordinate estreme della vista corrente della mappa</li>
<li>lo zoom corrente della mappa</li>
<li>un prompt contenente</li>
</ol>
<ul>
<li>un marker &quot;inclusivo&quot;, marcando quindi la posizione come inclusa (a couple of float - latitude and longitude)</li>
<li>un marker &quot;escludente&quot;, marcando quindi la posizione come esclusa (a couple of float - latitude and longitude)</li>
<li>a &quot;include&quot; rectangle (a couple of marker positions - north-east and south-west)</li>
</ul>
</li>
<li>
<p>a causa del vincoli posti dai CORS è difficile inviare direttamente richieste a risorse remote su domini diversi da quello di origine: per evitare questo problema ho preparato una semplice funzione proxy su Cloudflare (questo sito funziona con le Cloudflare Pages) che inoltra la richiesta dell'utente all'API remota</p>
</li>
<li>
<p>Il API Gateway inoltra la richiesta al backend serverless</p>
</li>
<li>
<p>La API Lambda serverless elabora la richiesta:</p>
<ol>
<li>analizza la richiesta e in particolare traduce il prompt con le coordinate di latitudine/longitudine in uno di coordinate x/y mappate sull'origine dell'immagine</li>
<li>scarica le <em>tiles</em> georeferenziate per poi unirle e ritagliarle</li>
<li>crea un'istanza del modello <a href="https://github.com/ChaoningZhang/MobileSAM" target="_blank" rel="noreferrer">MobileSam</a> se non esistente utilizzando OnnxRuntime come runtime</li>
<li>prepara il <a href="https://datasciencedojo.com/blog/embeddings-and-llm/#" target="_blank" rel="noreferrer"><em>embedding</em> delle immagini</a> e...</li>
<li>...avvia il processo di inferenza</li>
<li>trasforma il array di maschere di oggetti riconosciuti in un'unica immagine binaria quindi <a href="https://gisgeography.com/rasterization-vettorializzazione/" target="_blank" rel="noreferrer">vettorializza</a> la maschera binaria riconosciuta:
<ol>
<li>innanzitutto converte la maschera in un GeoDataframe GeoPandas utilizzando <a href="https://rasterio.readthedocs.io/en/stable/api/rasterio.features.html#rasterio.features.shapes" target="_blank" rel="noreferrer"><code>rasterio.features.shapes</code></a> e <a href="https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoDataFrame.from_features.html" target="_blank" rel="noreferrer"><code>GeoDataFrame.from_features</code></a></li>
<li>quindi esporta il nuovo Geodataframe in un geojson utilizzando <a href="https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoDataFrame.to_json.html" target="_blank" rel="noreferrer"><code>GeoDataFrame.to_json</code></a></li>
</ol>
</li>
</ol>
</li>
<li>
<p>la funzione proxy Cloudflare elabora la risposta e la inoltra al frontend</p>
</li>
<li>
<p>il frontend estrae dalla risposta il geojson che sarà poi caricato all'interno della WebMap. Sono anche aggiunte alcune informazioni come la <a href="https://xkcd.com/2867/" target="_blank" rel="noreferrer">durata non relativistica della risposta</a></p>
<p><img src="https://imgs.xkcd.com/comics/datetime.png" alt="datetime-diff"></p>
</li>
</ol>
<p>Per evitare abusi la <a href="https://ml-trinca.tornidor.com/prediction-map" target="_blank" rel="noreferrer">webmap dedicata a questo progetto sul mio sito personale</a> è sotto autenticazione (utilizzo <a href="https://auth0.com" target="_blank" rel="noreferrer">auth0.com</a>).</p>
<h2 id="architettura-della-demo-basata-su-fastapi" tabindex="-1">Architettura della demo basata su Fastapi <a class="header-anchor" href="#architettura-della-demo-basata-su-fastapi" aria-label="Permalink to &quot;Architettura della demo basata su Fastapi&quot;"></a></h2>
<p>Questa variante del progetto funziona in maniera abbastanza simile a <a href="/it/projects/samgis-segment-anything-applied-to-GIS.html#architettura-della-demo-aws">questa</a>. Queste le differenze principali:</p>
<ul>
<li><code>opencv-python</code> non è più fra le dipendenze di SamGIS dalla versione 1.5.1</li>
<li>il backend salva e riutilizza gli <em>embedding</em> delle immagini per evitare sprechi di potenza di calcolo e velocizzare il processo di inferenza</li>
<li>il frontend SPA (Single Page Application) è esposto direttamente dall'applicazione Fastapi</li>
</ul>
<p>Frontend e backend quindi risiedono sullo stesso dominio. Non servirà più quindi un proxy (la demo su AWS usava una <a href="https://developers.cloudflare.com/pages/functions/" target="_blank" rel="noreferrer">Page function di Cloudflare</a>) per fare richieste ad API su un dominio diverso.</p>
<p>Inoltre, posso mantenere <a href="https://huggingface.co/spaces/aletrn/samgis" target="_blank" rel="noreferrer">online questa demo</a> usando <a href="https://huggingface.co/pricing#hub" target="_blank" rel="noreferrer">HuggingFace gratuitamente</a>, rimuovendo la necessità di us sistema di autenticazione.</p>
<h2 id="aws-lambda-una-soluzione-serverless-alcuni-pro" tabindex="-1">AWS lambda, una soluzione serverless: alcuni pro... <a class="header-anchor" href="#aws-lambda-una-soluzione-serverless-alcuni-pro" aria-label="Permalink to &quot;AWS lambda, una soluzione serverless: alcuni pro...&quot;"></a></h2>
<ul>
<li>Può funzionare anche con immagini docker, molto utile nel caso di progetti complessi</li>
<li>Integrazione abbastanza semplice con altri servizi AWS e sistemi di autenticazione di terze parti</li>
<li>Servizio a consumo</li>
</ul>
<h2 id="e-alcuni-svantaggi" tabindex="-1">...e alcuni svantaggi <a class="header-anchor" href="#e-alcuni-svantaggi" aria-label="Permalink to &quot;...e alcuni svantaggi&quot;"></a></h2>
<ul>
<li>Configurazione non sempre facile (ho utilizzato <a href="https://docs.powertools.aws.dev/lambda/python/latest/" target="_blank" rel="noreferrer">Powertools for AWS Lambda (Python)</a> per migliorare la configurazione del mio logger)</li>
<li>Il modello serverless suggerito da AWS <a href="https://offbynone.io/issues/233/" target="_blank" rel="noreferrer">non sempre è la soluzione migliore</a></li>
<li>Le istanze Arm graviton EC2 che eseguono AWS lambda sono notevolmente limitate (<a href="https://github.com/microsoft/onnxruntime/issues/10038" target="_blank" rel="noreferrer">OnnxRuntime al momento non può essere eseguito su AWS lambda Arm e probabilmente non sarà mai supportato</a>).</li>
</ul>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[SamGIS - Segment Anything adattato al GIS]]></title>
            <link>/it/projects/samgis-segment-anything-applied-to-GIS</link>
            <guid isPermaLink="false">/it/projects/samgis-segment-anything-applied-to-GIS</guid>
            <pubDate>Mon, 23 Oct 2023 13:10:00 GMT</pubDate>
            <description><![CDATA[Scopri le funzionalità rivoluzionarie di Segment Anything grazie a SamGIS]]></description>
            <content:encoded><![CDATA[<h1 id="samgis-segment-anything-adattato-ai-sistemi-geografici-informativi-gis" tabindex="-1">SamGIS - Segment Anything adattato ai sistemi geografici informativi (GIS) <a class="header-anchor" href="#samgis-segment-anything-adattato-ai-sistemi-geografici-informativi-gis" aria-label="Permalink to &quot;SamGIS - Segment Anything adattato ai sistemi geografici informativi (GIS)&quot;"></a></h1>
<p>Questo progetto, composto da frontend <a href="https://en.wikipedia.org/wiki/Single-page_application" target="_blank" rel="noreferrer">SPA</a> e da una parte backend (<a href="https://github.com/trincadev/samgis-be/" target="_blank" rel="noreferrer">samgis-be</a>), è un tentativo di effettuare riconoscimento d'immagine basato sul machine learning su immagini derivanti da dati geo-spaziali anche senza l'utilizzo di capacità computazionali particolari. Di recente ho aggiunto la possibilità di partire da <a href="/it/projects/lisa-adapted-for-samgis.html">prompt testuali in linguaggio naturale</a>.</p>
<p>La prima versione del backend era modificata da <a href="https://github.com/vietanhdev/samexporter" target="_blank" rel="noreferrer">SAM Exporter</a> semplificando l'utilizzo di <a href="https://segment-anything.com/" target="_blank" rel="noreferrer">segment anything</a> al fine di migliorare il riconoscimento di poligoni in applicazioni web GIS.</p>
<p>Dalla versione 1.5.1 il backend integra modifiche mutuate da <a href="https://github.com/AndreyGermanov/sam_onnx_full_export" target="_blank" rel="noreferrer">sam_onnx_full_export</a>, in modo da supportare OnnxRuntime 1.17.x e versioni successive. Si noti che su MacOS l'esecuzione diretta da linea di comando soffre di <a href="https://github.com/microsoft/onnxruntime/issues/14455" target="_blank" rel="noreferrer">memory leak</a>, rendendo le operazioni di inferenza più lente di quanto sarebbe normale. Si consiglia quindi l'esecuzione del progetto dentro ad un container docker, a meno di voler fare attività di sviluppo e debug.</p>
<p>Questi sono i link alle risorse del progetto:</p>
<ul>
<li>una <a href="https://huggingface.co/spaces/aletrn/samgis" target="_blank" rel="noreferrer">demo di HuggingFace Space</a> pubblica</li>
<li>la <a href="https://docs.ml-trinca.tornidor.com" target="_blank" rel="noreferrer">documentazione e le specifiche AWS lambda OpenAPI</a></li>
</ul>
<p>Si tenga presente che la demo dello spazio HuggingFace ha i suoi file frontend statici nella cartella /static.</p>
<p>Chiunque desideri una spiegazione dettagliata sul funzionamento di questo progetto oppure voglia testare la demo sotto autenticazione può contattarmi tramite <a href="https://www.linkedin.com/in/trincatornidor/" target="_blank" rel="noreferrer">linkedin</a>.</p>
<h3 id="lisa-e-samgis" tabindex="-1">LISA e SamGIS <a class="header-anchor" href="#lisa-e-samgis" aria-label="Permalink to &quot;LISA e SamGIS&quot;"></a></h3>
<p>Ho anche aggiunto il supporto alla segmentazione d'immagine tramite prompt testuali in linguaggio naturale: qui i dettagli a proposito dell'<a href="/it/projects/lisa-adapted-for-samgis.html">integrazione di SamGIS con LISA</a>.</p>
<h2 id="samgis-rimpiazza-anche-surferdtm" tabindex="-1">SamGIS rimpiazza anche SurferDTM <a class="header-anchor" href="#samgis-rimpiazza-anche-surferdtm" aria-label="Permalink to &quot;SamGIS rimpiazza anche SurferDTM&quot;"></a></h2>
<p><a href="/it/projects/surferdtm-docs.html">SurferDTM</a> è un tentativo di trovare <a href="https://volcano.oregonstate.edu/stratovolcanoes" target="_blank" rel="noreferrer">stratovulcani</a> all'interno di modelli digitali del terreno (DEM/DTM). Come nuova funzionalità, SamGIS ora può utilizzare anche DEM di input (come la prima immagine in questa galleria):</p>
<div id="image-gallery-container-dem">
    <GalleryNoMap
      :galleryID="galleryNameDEM"
      :imagesData="imagesDataDEM"
    />
</div>
<p>Si tenga conto che il provider dei dati &quot;terrarium&quot; codifica le immagini come RGB, quindi il backend le elabora per ottenere le quote di terreno effettive (come nella seconda immagine in questa galleria) utilizzando questa formula (dalla <a href="https:// www.mapzen.com/blog/terrain-tile-service/">pagina del provider, mapzen/nextzen</a>):</p>
<blockquote>
<p>(red * 256) + green + (blue / 256) - 32768</p>
</blockquote>
<p>I also added a custom elaboration step to create an intermediate RGB image composed by channels:</p>
<ul>
<li>ROSSO - immagine DEM normalizzata</li>
<li>VERDE - immagine 2d normalizzata composta dalla risultante di
<ul>
<li>immagine DEM normalizzata</li>
<li>slope</li>
<li>curvatura</li>
</ul>
</li>
<li>BLU - curvatura</li>
</ul>
<div id="image-gallery-container-rgb2">
    <GalleryNoMap
      :galleryID="galleryNameRGB2"
      :imagesData="imagesDataRGB2"
    />
</div>
<p>Quest'immagine RGB infine sarà sottoposta a Segment Anything per l'inferenza.</p>
<details>
<summary><i>Apri questo elemento di dettaglio per visualizzare il json usato per creare le immagini intermedie DEM e RGB.</i></summary>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">   "bbox"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">         "ne"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.203706648934954</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.401906739170105</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">         "sw"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.12464369105346</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.231103669101747</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}},</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">   "prompt"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">238</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.158760519552146</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.306795638666486</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">250</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.152693546080975</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.289288652508679</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">264</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.16613515509541</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.319840059725284</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">272</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.161377438862836</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.288087192674299</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">285</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.174103408003454</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.308340372739261</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">293</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.17005996124035</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.298042145587583</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">   ],</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">   "zoom"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">13</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">   "source_type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"nextzen.terrarium"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
</div></details>
<h2 id="architettura-della-demo-aws" tabindex="-1">Architettura della demo AWS <a class="header-anchor" href="#architettura-della-demo-aws" aria-label="Permalink to &quot;Architettura della demo AWS&quot;"></a></h2>
<div id="contenitore-galleria-immagini">
     <GalleryNoMap
       :galleryID="nomegalleria"
       :imagesData="immaginiData"
     />
</div>
<ol>
<li>
<p>L'utente interagisce con una WebMap (nel mio esempio è una mappa leaflet che utilizza i dati cartografici di OpenStreetMap).</p>
</li>
<li>
<p>L'utente invia una POST ad un'<a href="https://docs.ml-trinca.tornidor.com/openapi" target="_blank" rel="noreferrer">API</a> utilizzando</p>
<ol>
<li>le coordinate estreme della vista corrente della mappa</li>
<li>lo zoom corrente della mappa</li>
<li>un prompt contenente</li>
</ol>
<ul>
<li>un marker &quot;inclusivo&quot;, marcando quindi la posizione come inclusa (a couple of float - latitude and longitude)</li>
<li>un marker &quot;escludente&quot;, marcando quindi la posizione come esclusa (a couple of float - latitude and longitude)</li>
<li>a &quot;include&quot; rectangle (a couple of marker positions - north-east and south-west)</li>
</ul>
</li>
<li>
<p>a causa del vincoli posti dai CORS è difficile inviare direttamente richieste a risorse remote su domini diversi da quello di origine: per evitare questo problema ho preparato una semplice funzione proxy su Cloudflare (questo sito funziona con le Cloudflare Pages) che inoltra la richiesta dell'utente all'API remota</p>
</li>
<li>
<p>Il API Gateway inoltra la richiesta al backend serverless</p>
</li>
<li>
<p>La API Lambda serverless elabora la richiesta:</p>
<ol>
<li>analizza la richiesta e in particolare traduce il prompt con le coordinate di latitudine/longitudine in uno di coordinate x/y mappate sull'origine dell'immagine</li>
<li>scarica le <em>tiles</em> georeferenziate per poi unirle e ritagliarle</li>
<li>crea un'istanza del modello <a href="https://github.com/ChaoningZhang/MobileSAM" target="_blank" rel="noreferrer">MobileSam</a> se non esistente utilizzando OnnxRuntime come runtime</li>
<li>prepara il <a href="https://datasciencedojo.com/blog/embeddings-and-llm/#" target="_blank" rel="noreferrer"><em>embedding</em> delle immagini</a> e...</li>
<li>...avvia il processo di inferenza</li>
<li>trasforma il array di maschere di oggetti riconosciuti in un'unica immagine binaria quindi <a href="https://gisgeography.com/rasterization-vettorializzazione/" target="_blank" rel="noreferrer">vettorializza</a> la maschera binaria riconosciuta:
<ol>
<li>innanzitutto converte la maschera in un GeoDataframe GeoPandas utilizzando <a href="https://rasterio.readthedocs.io/en/stable/api/rasterio.features.html#rasterio.features.shapes" target="_blank" rel="noreferrer"><code>rasterio.features.shapes</code></a> e <a href="https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoDataFrame.from_features.html" target="_blank" rel="noreferrer"><code>GeoDataFrame.from_features</code></a></li>
<li>quindi esporta il nuovo Geodataframe in un geojson utilizzando <a href="https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoDataFrame.to_json.html" target="_blank" rel="noreferrer"><code>GeoDataFrame.to_json</code></a></li>
</ol>
</li>
</ol>
</li>
<li>
<p>la funzione proxy Cloudflare elabora la risposta e la inoltra al frontend</p>
</li>
<li>
<p>il frontend estrae dalla risposta il geojson che sarà poi caricato all'interno della WebMap. Sono anche aggiunte alcune informazioni come la <a href="https://xkcd.com/2867/" target="_blank" rel="noreferrer">durata non relativistica della risposta</a></p>
<p><img src="https://imgs.xkcd.com/comics/datetime.png" alt="datetime-diff"></p>
</li>
</ol>
<p>Per evitare abusi la <a href="https://ml-trinca.tornidor.com/prediction-map" target="_blank" rel="noreferrer">webmap dedicata a questo progetto sul mio sito personale</a> è sotto autenticazione (utilizzo <a href="https://auth0.com" target="_blank" rel="noreferrer">auth0.com</a>).</p>
<h2 id="architettura-della-demo-basata-su-fastapi" tabindex="-1">Architettura della demo basata su Fastapi <a class="header-anchor" href="#architettura-della-demo-basata-su-fastapi" aria-label="Permalink to &quot;Architettura della demo basata su Fastapi&quot;"></a></h2>
<p>Questa variante del progetto funziona in maniera abbastanza simile a <a href="/it/projects/samgis-segment-anything-applied-to-GIS.html#architettura-della-demo-aws">questa</a>. Queste le differenze principali:</p>
<ul>
<li><code>opencv-python</code> non è più fra le dipendenze di SamGIS dalla versione 1.5.1</li>
<li>il backend salva e riutilizza gli <em>embedding</em> delle immagini per evitare sprechi di potenza di calcolo e velocizzare il processo di inferenza</li>
<li>il frontend SPA (Single Page Application) è esposto direttamente dall'applicazione Fastapi</li>
</ul>
<p>Frontend e backend quindi risiedono sullo stesso dominio. Non servirà più quindi un proxy (la demo su AWS usava una <a href="https://developers.cloudflare.com/pages/functions/" target="_blank" rel="noreferrer">Page function di Cloudflare</a>) per fare richieste ad API su un dominio diverso.</p>
<p>Inoltre, posso mantenere <a href="https://huggingface.co/spaces/aletrn/samgis" target="_blank" rel="noreferrer">online questa demo</a> usando <a href="https://huggingface.co/pricing#hub" target="_blank" rel="noreferrer">HuggingFace gratuitamente</a>, rimuovendo la necessità di us sistema di autenticazione.</p>
<h2 id="aws-lambda-una-soluzione-serverless-alcuni-pro" tabindex="-1">AWS lambda, una soluzione serverless: alcuni pro... <a class="header-anchor" href="#aws-lambda-una-soluzione-serverless-alcuni-pro" aria-label="Permalink to &quot;AWS lambda, una soluzione serverless: alcuni pro...&quot;"></a></h2>
<ul>
<li>Può funzionare anche con immagini docker, molto utile nel caso di progetti complessi</li>
<li>Integrazione abbastanza semplice con altri servizi AWS e sistemi di autenticazione di terze parti</li>
<li>Servizio a consumo</li>
</ul>
<h2 id="e-alcuni-svantaggi" tabindex="-1">...e alcuni svantaggi <a class="header-anchor" href="#e-alcuni-svantaggi" aria-label="Permalink to &quot;...e alcuni svantaggi&quot;"></a></h2>
<ul>
<li>Configurazione non sempre facile (ho utilizzato <a href="https://docs.powertools.aws.dev/lambda/python/latest/" target="_blank" rel="noreferrer">Powertools for AWS Lambda (Python)</a> per migliorare la configurazione del mio logger)</li>
<li>Il modello serverless suggerito da AWS <a href="https://offbynone.io/issues/233/" target="_blank" rel="noreferrer">non sempre è la soluzione migliore</a></li>
<li>Le istanze Arm graviton EC2 che eseguono AWS lambda sono notevolmente limitate (<a href="https://github.com/microsoft/onnxruntime/issues/10038" target="_blank" rel="noreferrer">OnnxRuntime al momento non può essere eseguito su AWS lambda Arm e probabilmente non sarà mai supportato</a>).</li>
</ul>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[SamGIS - Segment Anything applied to GIS]]></title>
            <link>/projects/samgis-segment-anything-applied-to-GIS</link>
            <guid isPermaLink="false">/projects/samgis-segment-anything-applied-to-GIS</guid>
            <pubDate>Mon, 23 Oct 2023 13:10:00 GMT</pubDate>
            <description><![CDATA[Discover the groundbreaking capabilities of Segment Anything thanks to SamGIS]]></description>
            <content:encoded><![CDATA[<h1 id="samgis-segment-anything-applied-to-geographic-information-systems-gis" tabindex="-1">SamGIS - Segment Anything applied to Geographic Information Systems (GIS) <a class="header-anchor" href="#samgis-segment-anything-applied-to-geographic-information-systems-gis" aria-label="Permalink to &quot;SamGIS - Segment Anything applied to Geographic Information Systems (GIS)&quot;"></a></h1>
<p>This project, composed by a <a href="https://en.wikipedia.org/wiki/Single-page_application" target="_blank" rel="noreferrer">SPA</a> frontend and by a backend (<a href="https://github.com/trincadev/samgis-be/" target="_blank" rel="noreferrer">samgis-be</a>), is an attempt to perform machine learning image segmentation on geo-spatial data even without the use of dedicated graphics cards. I recently added the ability to start from <a href="https://trinca.tornidor.com/projects/lisa-adapted-for-samgis" target="_blank" rel="noreferrer">natural language text prompts</a>.</p>
<p>I wrote the backend adapting <a href="https://github.com/vietanhdev/samexporter" target="_blank" rel="noreferrer">SAM Exporter</a> with the aim to use the machine learning <a href="https://segment-anything.com/" target="_blank" rel="noreferrer">segment anything</a> project for the purpose to improve polygons recognition in GIS web applications.</p>
<p>Starting from version 1.5.1 the backend integrates changes borrowed from <a href="https://github.com/AndreyGermanov/sam_onnx_full_export" target="_blank" rel="noreferrer">sam_onnx_full_export</a>, to support OnnxRuntime 1.17.x and later versions. Please note that on MacOS directly running the project from the command line suffers from <a href="https://github.com/microsoft/onnxruntime/issues/14455" target="_blank" rel="noreferrer">memory leaks</a>, making inference operations slower than normal. It's best therefore running the project inside a docker container, unless in case of development or debugging activities.</p>
<p>These are the project resource links:</p>
<ul>
<li>a public <a href="https://huggingface.co/spaces/aletrn/samgis" target="_blank" rel="noreferrer">HuggingFace space demo</a></li>
<li>the <a href="https://docs.ml-trinca.tornidor.com" target="_blank" rel="noreferrer">documentation and AWS lambda OpenAPI specs</a></li>
</ul>
<p>Note that the HuggingFace space demo has its static frontend files under the /static folder.</p>
<p>Ask me for a user via <a href="https://www.linkedin.com/in/trincatornidor/" target="_blank" rel="noreferrer">linkedin</a> if you would like to test the authenticated demo or an explanation about the code.</p>
<h3 id="lisa-and-samgis" tabindex="-1">LISA and SamGIS <a class="header-anchor" href="#lisa-and-samgis" aria-label="Permalink to &quot;LISA and SamGIS&quot;"></a></h3>
<p>I also added support for image segmentation via natural language text prompts: here are the details about the <a href="/projects/lisa-adapted-for-samgis.html">SamGIS integration with LISA</a>.</p>
<h2 id="samgis-also-replaces-surferdtm" tabindex="-1">SamGIS also replaces SurferDTM <a class="header-anchor" href="#samgis-also-replaces-surferdtm" aria-label="Permalink to &quot;SamGIS also replaces SurferDTM&quot;"></a></h2>
<p><a href="/projects/surferdtm-docs.html">SurferDTM</a> was an attempt to find <a href="https://volcano.oregonstate.edu/stratovolcanoes" target="_blank" rel="noreferrer">stratovolcanoes</a> within digital terrain models (DEM/DTM). As a new feature SamGIS now can now use also input DEM images (like the first image in this gallery):</p>
<div id="image-gallery-container-dem">
    <GalleryNoMap
      :galleryID="galleryNameDEM"
      :imagesData="imagesDataDEM"
    />
</div>
<p>Note that the &quot;terrarium&quot; source provider data encode the image as RGB, so the backend processes it to obtain the elevation data (like the second image in this gallery) using this formula (from the <a href="https://www.mapzen.com/blog/terrain-tile-service/">map tile provider page</a>):</p>
<blockquote>
<p>(red * 256) + green + (blue / 256) - 32768</p>
</blockquote>
<p>I also added a custom elaboration step to create an intermediate RGB image composed by channels:</p>
<ul>
<li>RED - normalized DEM image</li>
<li>GREEN - normalized 2d composed image by
<ul>
<li>normalized DEM image</li>
<li>slope</li>
<li>curvature</li>
</ul>
</li>
<li>BLUE - curvature</li>
</ul>
<div id="image-gallery-container-rgb2">
    <GalleryNoMap
      :galleryID="galleryNameRGB2"
      :imagesData="imagesDataRGB2"
    />
</div>
<p>Segment Anything will finally do the inference on this rgb image.</p>
<details>
<summary><i>Open this element detail to view the json request I use to create the DEM and RGB intermediate images.</i></summary>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">   "bbox"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">         "ne"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.203706648934954</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.401906739170105</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">         "sw"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.12464369105346</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.231103669101747</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}},</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">   "prompt"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">238</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.158760519552146</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.306795638666486</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">250</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.152693546080975</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.289288652508679</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">264</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.16613515509541</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.319840059725284</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">272</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.161377438862836</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.288087192674299</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">285</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.174103408003454</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.308340372739261</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">},</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">         {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">293</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"point"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lat"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">46.17005996124035</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"lng"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.298042145587583</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"label"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">   ],</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">   "zoom"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">13</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">   "source_type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"nextzen.terrarium"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
</div></details>
<h2 id="project-architecture-of-aws-demo" tabindex="-1">Project Architecture of AWS demo <a class="header-anchor" href="#project-architecture-of-aws-demo" aria-label="Permalink to &quot;Project Architecture of AWS demo&quot;"></a></h2>
<div id="image-gallery-container-arch">
    <GalleryNoMap
      :galleryID="galleryNameArch"
      :imagesData="imagesDataArch"
    />
</div>
<ol>
<li>
<p>The user interact with a WebMap (in my example it's a leaflet map that use OpenStreetMap map data).</p>
</li>
<li>
<p>The user send a POST request to an <a href="https://docs.ml-trinca.tornidor.com/openapi" target="_blank" rel="noreferrer">API</a> using</p>
<ol>
<li>the current map extent</li>
<li>the current map zoom</li>
<li>a prompt array of objects; objects could be
<ul>
<li>an &quot;include&quot; marker position (a couple of float - latitude and longitude)</li>
<li>an &quot;exclusionary&quot; marker position (a couple of float - latitude and longitude)</li>
<li>a &quot;include&quot; rectangle (two latitude and longitude positions - north-east and south-west)</li>
</ul>
</li>
</ol>
</li>
<li>
<p>because of CORS constrain it's difficult to directly send requests to remote resources on different domains: to avoid this problems I prepared a simple Cloudflare proxy function that forward the user request to the remote API</p>
</li>
<li>
<p>The API Gateway proxies the request to the serverless backend</p>
</li>
<li>
<p>the serverless Lambda API elaborate the request:</p>
<ol>
<li>it parses the request and in particular translate the input latitude/longitude prompt into a x/y (pixel coordinates) one</li>
<li>it downloads the geo-referenced tiles then merge and crop them</li>
<li>it instantiates a <a href="https://github.com/ChaoningZhang/MobileSAM" target="_blank" rel="noreferrer">MobileSam</a> model instance if not already created using OnnxRuntime as runtime</li>
<li>it prepares the <a href="https://datasciencedojo.com/blog/embeddings-and-llm/#" target="_blank" rel="noreferrer">image embedding</a> and...</li>
<li>...it starts the image inference process</li>
<li>it transforms the recognition masks into a single binary mask then it <a href="https://gisgeography.com/rasterization-vectorization/" target="_blank" rel="noreferrer">vectorizes</a> the recognized binary mask:
<ol>
<li>first it converts the mask into a GeoPandas GeoDataframe using <a href="https://rasterio.readthedocs.io/en/stable/api/rasterio.features.html#rasterio.features.shapes" target="_blank" rel="noreferrer"><code>rasterio.features.shapes</code></a> and <a href="https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoDataFrame.from_features.html" target="_blank" rel="noreferrer"><code>GeoDataFrame.from_features</code></a></li>
<li>then it convert the new Geodataframe into a geojson using <a href="https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoDataFrame.to_json.html" target="_blank" rel="noreferrer"><code>GeoDataFrame.to_json</code></a></li>
</ol>
</li>
</ol>
</li>
<li>
<p>the Cloudflare proxy function parses the response and forward it to the frontend</p>
</li>
<li>
<p>the frontend parse the geojson to load within the WebMap and shows some response info like the <a href="https://xkcd.com/2867/" target="_blank" rel="noreferrer">non-relativistic response duration time</a>:</p>
<p><img src="https://imgs.xkcd.com/comics/datetime.png" alt="datetime-diff"></p>
</li>
</ol>
<p>To avoid abuse the <a href="https://ml-trinca.tornidor.com/prediction-map" target="_blank" rel="noreferrer">prompt input web map that I host on my personal site</a> is under authentication (I use <a href="https://auth0.com" target="_blank" rel="noreferrer">auth0.com</a>).</p>
<h2 id="fastapi-based-demo-architecture" tabindex="-1">Fastapi-based demo architecture <a class="header-anchor" href="#fastapi-based-demo-architecture" aria-label="Permalink to &quot;Fastapi-based demo architecture&quot;"></a></h2>
<p>This project variant works almost as <a href="/projects/samgis-segment-anything-applied-to-GIS.html#project-architecture-of-aws-demo">this</a>. There are these differences:</p>
<ul>
<li><code>opencv-python</code> is not anymore a SamGIS project dependency</li>
<li>the server now saves and re-uses image <em>embeddings</em> to avoid resource waste and speed up the inference process</li>
<li>SPA frontend now is integrated directly within the Fastapi backend</li>
</ul>
<p>Frontend and backend now are on the same domain. Therefore there is no longer use for a proxy (the AWS demo used a <a href="https://developers.cloudflare.com/pages/functions/" target="_blank" rel="noreferrer">Cloudflare Pages function</a>) to make requests to APIs on a different domain.</p>
<p>Additionally, since I can <a href="https://huggingface.co/spaces/aletrn/samgis" target="_blank" rel="noreferrer">host this Fastapi demo</a> for <a href="https://huggingface.co/pricing#hub" target="_blank" rel="noreferrer">free on HuggingFace</a>, I don't need anymore an authentication system.</p>
<h2 id="aws-lambda-a-serverless-solution-some-pros" tabindex="-1">AWS lambda, a serverless solution: some pros... <a class="header-anchor" href="#aws-lambda-a-serverless-solution-some-pros" aria-label="Permalink to &quot;AWS lambda, a serverless solution: some pros...&quot;"></a></h2>
<ul>
<li>It can work also with docker images for complex or big projects (like this)</li>
<li>Almost easy, working out-of-the-box integration with other AWS services and third-parts authentication systems</li>
<li>Pay-as-you-go service</li>
</ul>
<h2 id="and-some-cons" tabindex="-1">...and some cons <a class="header-anchor" href="#and-some-cons" aria-label="Permalink to &quot;...and some cons&quot;"></a></h2>
<ul>
<li>Setup not always easy (I used <a href="https://docs.powertools.aws.dev/lambda/python/latest/" target="_blank" rel="noreferrer">Powertools for AWS Lambda (Python)</a> to improve my logger setup)</li>
<li>The serverless model suggested by AWS <a href="https://offbynone.io/issues/233/" target="_blank" rel="noreferrer">not always it's the best solution</a></li>
<li>Arm graviton EC2 instances that run AWS lambda are greatly limited (<a href="https://github.com/microsoft/onnxruntime/issues/10038" target="_blank" rel="noreferrer">OnnxRuntime at the moment cannot run on Arm AWS lambda and probably will never run</a>).</li>
</ul>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[SurferDTM]]></title>
            <link>/it/projects/surferdtm-docs</link>
            <guid isPermaLink="false">/it/projects/surferdtm-docs</guid>
            <pubDate>Mon, 16 Oct 2023 21:15:00 GMT</pubDate>
            <description><![CDATA[Funzionamento e descrizione di SurferDTM, uno strumento per la ricerca di edifici vulcanici in modelli di quota del terreno (DTM)]]></description>
            <content:encoded><![CDATA[<h1 id="surferdtm" tabindex="-1">SurferDTM <a class="header-anchor" href="#surferdtm" aria-label="Permalink to &quot;SurferDTM&quot;"></a></h1>
<h2 id="integrazione-con-samgis" tabindex="-1">Integrazione con SamGIS <a class="header-anchor" href="#integrazione-con-samgis" aria-label="Permalink to &quot;Integrazione con SamGIS&quot;"></a></h2>
<p>Al momento ho gestisco queste attività di analisi d'immagine utilizzando <a href="/projects/samgis-segment-anything-applied-to-GIS.html">SamGIS</a>. Ecco due casi d'uso per effettuare instance segmentation in modo automatico dell'Etna utilizzando due diversi <em>tiles providers</em> (<a href="https://www.openstreetmap.org/#map=9/37.5587/14.0982&amp;layers=HN" target="_blank" rel="noreferrer">OpenStreetMap HOT</a>) e (<a href="https://www.mapzen.com/blog/terrain-tile-service/" target="_blank" rel="noreferrer">Nextzen /Terrario Mapzen</a>).</p>
<h3 id="openstreetmap-hot" tabindex="-1">OpenStreetMap HOT <a class="header-anchor" href="#openstreetmap-hot" aria-label="Permalink to &quot;OpenStreetMap HOT&quot;"></a></h3>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Nn1NasHNq-U?si=psJuXNo7Qz80K3-m" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<h3 id="nextzen-mapzen-terrarium" tabindex="-1">Nextzen/Mapzen terrarium <a class="header-anchor" href="#nextzen-mapzen-terrarium" aria-label="Permalink to &quot;Nextzen/Mapzen terrarium&quot;"></a></h3>
<iframe width="560" height="315" src="https://www.youtube.com/embed/-1tPx6E8rwE?si=i6rSFjbo6uT0Saao" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<h2 id="basi-scientifiche-del-vecchio-progetto-una-specie-di" tabindex="-1">Basi scientifiche del vecchio progetto (una specie di...) <a class="header-anchor" href="#basi-scientifiche-del-vecchio-progetto-una-specie-di" aria-label="Permalink to &quot;Basi scientifiche del vecchio progetto (una specie di...)&quot;"></a></h2>
<p>Questo piccolo progetto prosegue idealmente il mio <a href="/education.html#master-degree">lavoro di tesi di laurea magistrale</a>.
Per portare questa idea al livello successivo (e per provare in prima persona alcune tecnologie interessanti) ho creato questo progetto fullstack.
Fondamentalmente inizio estraendo il contorno di un'area geografica (per ora parto semplicemente dalla lista di coordinate di vulcani noti a livello mondiale). Utilizzo queste coordinate per preparare un <a href="https://www.sciencedirect.com/topics/earth-and-planetary-sciences/digital-terrain-model" target="_blank" rel="noreferrer">raster DTM</a>,
poi elaborato in un'immagine RGB (pendenza - elevazione - curvatura) che sottopongo ad un modello di machine learning.
L'output del modello di machine learning è una (o più) lista di coordinate che posso geo-referenziare e rappresentare in una <em>webmap</em>.</p>
<p>Ho basato questo lavoro su <a href="https://github.com/tilezen/joerd/blob/master/docs/data-sources.md" target="_blank" rel="noreferrer">Mapzen Terrain Tiles</a> perché
questo set di dati contiene dati di elevazione sia terrestri che batimetrici.</p>
<p>Al momento ho sostituito lo stack tecnologico alla base di SurferDTM con <a href="/projects/samgis-segment-anything-applied-to-GIS.html">SamGIS</a>, offrendo prestazioni migliori ed essendo più facile da gestire.</p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[SurferDTM]]></title>
            <link>/projects/surferdtm-docs</link>
            <guid isPermaLink="false">/projects/surferdtm-docs</guid>
            <pubDate>Mon, 16 Oct 2023 21:15:00 GMT</pubDate>
            <description><![CDATA[Usage and description of SurferDTM, a tool for searching for volcanic buildings in terrain elevation models (DTM)]]></description>
            <content:encoded><![CDATA[<h1 id="surferdtm" tabindex="-1">SurferDTM <a class="header-anchor" href="#surferdtm" aria-label="Permalink to &quot;SurferDTM&quot;"></a></h1>
<h2 id="integration-with-samgis" tabindex="-1">Integration with SamGIS <a class="header-anchor" href="#integration-with-samgis" aria-label="Permalink to &quot;Integration with SamGIS&quot;"></a></h2>
<p>Right now I solved these image segmentation tasks using <a href="/projects/samgis-segment-anything-applied-to-GIS.html">SamGIS</a>. Here two a use case for automatic instance segmentation of Etna volcano using two different tile providers (<a href="https://www.openstreetmap.org/#map=9/37.5587/14.0982&amp;layers=HN" target="_blank" rel="noreferrer">OpenStreetMap HOT</a>) and (<a href="https://www.mapzen.com/blog/terrain-tile-service/" target="_blank" rel="noreferrer">Nextzen/Mapzen terrarium</a>).</p>
<h3 id="openstreetmap-hot" tabindex="-1">OpenStreetMap HOT <a class="header-anchor" href="#openstreetmap-hot" aria-label="Permalink to &quot;OpenStreetMap HOT&quot;"></a></h3>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Nn1NasHNq-U?si=psJuXNo7Qz80K3-m" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<h3 id="nextzen-mapzen-terrarium" tabindex="-1">Nextzen/Mapzen terrarium <a class="header-anchor" href="#nextzen-mapzen-terrarium" aria-label="Permalink to &quot;Nextzen/Mapzen terrarium&quot;"></a></h3>
<iframe width="560" height="315" src="https://www.youtube.com/embed/-1tPx6E8rwE?si=i6rSFjbo6uT0Saao" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<h2 id="scientific-basis-of-the-old-project-sort-of" tabindex="-1">Scientific basis of the old project (sort of...) <a class="header-anchor" href="#scientific-basis-of-the-old-project-sort-of" aria-label="Permalink to &quot;Scientific basis of the old project (sort of...)&quot;"></a></h2>
<p>This little project ideally prosecute my <a href="/education.html#master-degree">Master Degree's work thesis</a>.
To bring this idea to the next level (and to try first-hand some cool technologies) I created this fullstack project.
Basically I start extracting a geographic area polygon (right now I simply start from the list of volcanoes known worldwide). I use these coordinates to prepare a <a href="https://www.sciencedirect.com/topics/earth-and-planetary-sciences/digital-terrain-model" target="_blank" rel="noreferrer">DTM raster</a>, then elaborated in an RGB (slope - elevation - curvature) image that I submit to a machine learning instance detection.
The machine learning model response it's one (or more) array(s) of coordinates points that I can georeference and show on my
frontend map.</p>
<p>I have replaced the underlying technology stack of SurferDTM with <a href="/projects/samgis-segment-anything-applied-to-GIS.html">SamGIS</a> because it offers better performance and it's easier to manage.</p>
<!--
## SurferDTM synthetic volcanoes images generation

### TODO

    volcanoes: randint x:
         - octaves: [1,2, None] octaves2: [5,6, None] octaves3: [9,10, None] 
         - step erosion [0,1600]
    
    mountain: randint x:
         - octaves: [1,2] octaves2: [3,4] octaves3: [6,7], octaves4: [9,10]
         - step erosion [2400, 6400]

- analisi features statistiche dei vulcani:
  - Nevado de Toluca
  - Popocatepetl
  - Iztaccíhuatl
  - Cotopaxi
  - Mt. Fuji
  - Etna
- analisi features statistiche del picco montuoso: Aconcagua
- generazione vulcani sintetici usando varie coppie di feature statistiche (h, p) estratte dall'analisi di cui sopra
- creazione paesaggi composti sia da vulcani sia da picchi montuosi sintetici. Vulcani o rilievi montuosi piazzati
  dentro: https://stackoverflow.com/questions/47373311/randomly-sample-sub-arrays-from-a-2d-array-in-python

### DONE

- analisi features statistiche di un primo vulcano (La Malinche)
- analisi features statistiche di un primo picco montuoso con caratteristiche similari ad un edificio vulcanico (
  Cervino)
- generazione di vulcani sintetici (completi di perlin noise ed erosione) sfruttando le caratteristiche base dedotte dal
  retro fitting sulla Malinche
- generazione di rilievi montuosi sintetici (completi di perlin noise ed erosione) basate sulle caratteristiche del
  Cervino
- modificato algoritmo generazione picchi montuosi:
  - picco con h=1 in posizione centrale (±10% distanza vs centro raster)
  - dal picco h=1 decise quante rotazioni: 2 o 3 volte per 1-4 punti aggiunti
    - 2 biforcazioni: a~= 90° - 180° (tot ~180°)
    - 3 biforcazioni: a~= 40° - 94° (tot ~240°)
- fn per:
  - ritagliare picco montuoso (che criterio per scelta del contorno? rottura di pendenza?)
  - dimensionarlo usando come riferimento la dimensione maggiore, scalando proporzionalmente
  - pad della dimensione minore fino a rendere l'immagine quadrata

### Dettagli sulla generazione di vulcani sintetici
- effettuata instance segmentation usando Detectron2 di Meta/Facebook Labs, salvando il dataset in formato COCO
- duomi su coordinate randomiche vicino agli stratovulcani
- scelte mediante un gran numero di prove ed errore 26 classi di stratovulcani: combinazioni di livelli di erosione, eccentricità, raggio del cratere sommitale, numero di duomi circorsanti
- aggiunta di creste/depressioni radiali intorno al cratere sommitale sui fianchi del vulcano
- erosione: a seconda del livello di erosione, sono applicati prima rumore "pnoise" (https://pypi.org/project/noise/), poi simulata evoluzione del DTM con terrainbento
- per rendere la "texture" del vulcano differente dal background circostante evitando al contempo artefatti lineari facilmente riconoscibili sono adattati:
  - la superficie del vulcano "ritagliata" (usato "weights" nel metodo surferdtm.rasters.segmentation.fix_h_object_raster_mask, "weights" prodotto nel metodo from surferdtm.rasters.evolution.recursive_get_weights_distance_transform_gaussian)
  - il background DTM (due step di interpolazione, nel metodo surferdtm.rasters.segmentation.fix_foreground_base_raster_mask)

#### Vedi HTML callgraph
1. **Generazione del vulcano**
   1. scelta dei parametri (erosione, eccentricità, raggio del cratere sommitale, numero di duomi circorsanti)
   2. generazione della superficie di un vulcano basata sul metodo di P.Grosse (https://link.springer.com/article/10.1007/s00445-019-1336-3#Sec12, https://www.semanticscholar.org/paper/A-global-database-of-composite-volcano-morphometry-Grosse-Euillades/785a3b66598a7faf1398bba9603c14e46f3cc103)
   3. preparazione di una maschera di "weights"
   4. generazione delle creste/canaloni
   5. spostamento alle coordinate scelte
   6. aggiunta del rumore pnoise
   7. applicata erosione usando landlab
   8. aggiunta degli eventuali duomi circostanti il vulcano
2. **integrazione del vulcano nel background reale prescelto**:
   1. preparazione del vulcano "ritagliato" (usato "weights" nel metodo surferdtm.rasters.segmentation.fix_h_object_raster_mask, "weights" prodotto nel metodo from surferdtm.rasters.evolution.recursive_get_weights_distance_transform_gaussian)
   2. preparazione del background DTM reale (due step di interpolazione, nel metodo surferdtm.rasters.segmentation.fix_foreground_base_raster_mask)
3. **preparazione di una immagine RGB usando (proporzione: surferdtm.constants.volcanoes_objects.CHANNEL_EXAGGERATIONS_LIST_COMBO?)**
   1. superficie DTM con il vulcano sintetico di cui ai punti 1 e 2
   2. media di
      1. superficie DTM con il vulcano sintetico di cui ai punti 1 e 2
      2. slope (surferdtm.rasters.derivatives.get_slope_curvature)
      3. curvatura (surferdtm.rasters.derivatives.get_slope_curvature)
   3. curvatura
4. **estrazione delle informazioni riguardanti il poligono contenente il vulcano ritagliato, salvate in un dataset COCO json**

### clusterizzazione di dati statistici estrapolati da aree montuose e vulcaniche
Invece che analizzare le i poligoni delle aree montuose, si potrebbe effettuare campionamento delle aree montuose stesse:
1. resize dell'area montuosa (al 10%?) da utilizzare?
2. punti massimi locali (in numero definito in base alla % dell'area), massimi che devono avere
  1. delta di quota minima definita
  2. distanza fra uno e l'altro minima definita
3. intorno ad ogni massimo locale scelto si estrarrà un riquadro di lato definito, su cui effettuare l'analisi

### ADDESTRAMENTO DELLA RETE NEURALE DI RICONOSCIMENTO DEGLI EDIFICI VULCANICI

1. classificazione con vulcani sintetici e picchi montuosi singoli
2. segmentazione paesaggi di vulcani e picchi vulcanici misti (sintetici)
3. segmentazione paesaggi di vulcani e picchi vulcanici misti (reali)
4. controllo vulcani individuati usando lo shp dello smithsonian
5. ripetere punti 2, 3, 4 sfruttando vulcani falsi posivi e falsi negativi per migliorare immagini sintetiche per
   l'addestramento della segmentazione

### Esecuzione/test del modello ML basato su Detectron2
Eseguire il modello basato su Detectron2 dentro a un'immagine docker, quindi quuest'immagine esporrà delle API in
localhost invocate dal frontend.
-->]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Una camminata verso Fuentes (Colico)]]></title>
            <link>/it/photo-galleries/colico-20231001</link>
            <guid isPermaLink="false">/it/photo-galleries/colico-20231001</guid>
            <pubDate>Sun, 01 Oct 2023 10:25:00 GMT</pubDate>
            <description><![CDATA[Una rilassante passeggiata sul sentiero Valtellina partendo da Colico]]></description>
            <content:encoded><![CDATA[<h1 id="colico-1-ottobre-2023" tabindex="-1">Colico, 1 ottobre 2023 <a class="header-anchor" href="#colico-1-ottobre-2023" aria-label="Permalink to &quot;Colico, 1 ottobre 2023&quot;"></a></h1>
<h2 id="una-camminata-verso-fuentes-colico" tabindex="-1">Una camminata verso Fuentes (Colico) <a class="header-anchor" href="#una-camminata-verso-fuentes-colico" aria-label="Permalink to &quot;Una camminata verso Fuentes (Colico)&quot;"></a></h2>
<div id="map-container">
    <MapWithPolyline
      :center='[46.144436, 9.377447]'
      location="Colico"
      :mapName="mapName"
      :pathUrl="`/paths/${mapName}.json`"
      :photosUrl="`/photos/${mapName}.json`"
      zoom=15
    />
</div>
<h2 id="alcune-foto-vicino-a-fuentes-colico" tabindex="-1">Alcune foto vicino a Fuentes (Colico) <a class="header-anchor" href="#alcune-foto-vicino-a-fuentes-colico" aria-label="Permalink to &quot;Alcune foto vicino a Fuentes (Colico)&quot;"></a></h2>
<p>Foto scattate il 1 ottobre 2023.</p>
<div id="image-gallery-container">
    <SimpleGallery
      :galleryID="mapName"
      :imagesData="imagesData"
    />
</div>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[A walk towards Fuentes (Colico)]]></title>
            <link>/photo-galleries/colico-20231001</link>
            <guid isPermaLink="false">/photo-galleries/colico-20231001</guid>
            <pubDate>Sun, 01 Oct 2023 10:25:00 GMT</pubDate>
            <description><![CDATA[A relaxing walk on the Valtellina footpath starting from Colico]]></description>
            <content:encoded><![CDATA[<h1 id="colico-1-october-2023" tabindex="-1">Colico, 1 October 2023 <a class="header-anchor" href="#colico-1-october-2023" aria-label="Permalink to &quot;Colico, 1 October 2023&quot;"></a></h1>
<h2 id="a-walk-towards-fuentes-colico" tabindex="-1">A walk towards Fuentes (Colico) <a class="header-anchor" href="#a-walk-towards-fuentes-colico" aria-label="Permalink to &quot;A walk towards Fuentes (Colico)&quot;"></a></h2>
<div id="map-container">
    <MapWithPolyline
      :center='[46.144436, 9.377447]'
      location="Colico"
      :mapName="mapName"
      :pathUrl="`/paths/${mapName}.json`"
      :photosUrl="`/photos/${mapName}.json`"
      zoom=15
    />
</div>
<h2 id="some-photos-near-fuentes-colico" tabindex="-1">Some photos near Fuentes (Colico) <a class="header-anchor" href="#some-photos-near-fuentes-colico" aria-label="Permalink to &quot;Some photos near Fuentes (Colico)&quot;"></a></h2>
<p>Photos taken on 1 October 2023.</p>
<div id="image-gallery-container">
    <SimpleGallery
      :galleryID="mapName"
      :imagesData="imagesData"
    />
</div>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Tornando in Val Masino]]></title>
            <link>/it/photo-galleries/val-masino-20230924</link>
            <guid isPermaLink="false">/it/photo-galleries/val-masino-20230924</guid>
            <pubDate>Sun, 24 Sep 2023 09:40:00 GMT</pubDate>
            <description><![CDATA[Alcuni scenari mozzafiato della Riserva Naturale della Val Masino]]></description>
            <content:encoded><![CDATA[<h1 id="val-masino-24-settembre-2023" tabindex="-1">Val Masino, 24 Settembre 2023 <a class="header-anchor" href="#val-masino-24-settembre-2023" aria-label="Permalink to &quot;Val Masino, 24 Settembre 2023&quot;"></a></h1>
<h2 id="tornando-in-val-masino" tabindex="-1">Tornando in Val Masino <a class="header-anchor" href="#tornando-in-val-masino" aria-label="Permalink to &quot;Tornando in Val Masino&quot;"></a></h2>
<div id="map-container">
    <MapWithPolyline
      :center='[46.251952, 9.645843]'
      location="Val Masino"
      :mapName="mapName"
      :pathUrl="`/paths/${mapName}.json`"
      :photosUrl="`/photos/${mapName}.json`"
      zoom=14
    />
</div>
<h2 id="altre-trentasei-vedute-della-val-masino" tabindex="-1">Altre trentasei vedute della Val Masino <a class="header-anchor" href="#altre-trentasei-vedute-della-val-masino" aria-label="Permalink to &quot;Altre trentasei vedute della Val Masino&quot;"></a></h2>
<p>Foto scattate il 24 Settembre 2023.</p>
<div id="image-gallery-container">
    <SimpleGallery
      :galleryID="mapName"
      :imagesData="imagesData"
    />
</div>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Going back to Val Masino]]></title>
            <link>/photo-galleries/val-masino-20230924</link>
            <guid isPermaLink="false">/photo-galleries/val-masino-20230924</guid>
            <pubDate>Sun, 24 Sep 2023 09:40:00 GMT</pubDate>
            <description><![CDATA[Some breathtaking scenery of the Val Masino Nature Reserve]]></description>
            <content:encoded><![CDATA[<h1 id="val-masino-24-september-2023" tabindex="-1">Val Masino, 24 September 2023 <a class="header-anchor" href="#val-masino-24-september-2023" aria-label="Permalink to &quot;Val Masino, 24 September 2023&quot;"></a></h1>
<h2 id="going-back-to-val-masino" tabindex="-1">Going back to Val Masino <a class="header-anchor" href="#going-back-to-val-masino" aria-label="Permalink to &quot;Going back to Val Masino&quot;"></a></h2>
<div id="map-container">
    <MapWithPolyline
      :center='[46.251952, 9.645843]'
      location="Val Masino"
      :mapName="mapName"
      :pathUrl="`/paths/${mapName}.json`"
      :photosUrl="`/photos/${mapName}.json`"
      zoom=14
    />
</div>
<h2 id="let-s-take-again-thirty-six-views-of-val-masino" tabindex="-1">Let's take again thirty-six views of Val Masino <a class="header-anchor" href="#let-s-take-again-thirty-six-views-of-val-masino" aria-label="Permalink to &quot;Let's take again thirty-six views of Val Masino&quot;"></a></h2>
<p>Photos taken on 24 September 2023.</p>
<div id="image-gallery-container">
    <SimpleGallery
      :galleryID="mapName"
      :imagesData="imagesData"
    />
</div>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[From leaflet popup marker to photo gallery image and back]]></title>
            <link>/blog/from-leaflet-popup-marker-to-photogallery-and-back</link>
            <guid isPermaLink="false">/blog/from-leaflet-popup-marker-to-photogallery-and-back</guid>
            <pubDate>Tue, 05 Sep 2023 00:50:56 GMT</pubDate>
            <description><![CDATA[Advises about integration of Photswipe, Vitepress and Leaflet]]></description>
            <content:encoded><![CDATA[<h1 id="from-marker-map-to-photo-gallery-image-and-back" tabindex="-1">From marker map to photo gallery image and back <a class="header-anchor" href="#from-marker-map-to-photo-gallery-image-and-back" aria-label="Permalink to &quot;From marker map to photo gallery image and back&quot;"></a></h1>
<p>When I go hiking often I take photos of the landscape I cross. For this reason I try to prepare some gps tracks for my walks and excursions, so I started adding markers to show the position where I took the photos.</p>
<p>I display my photos using <a href="https://photoswipe.com/" target="_blank" rel="noreferrer">PhotoSwipe</a> and I use <a href="https://leafletjs.com/" target="_blank" rel="noreferrer">Leaflet</a> to handle geographical maps. From a technical point of view I display on the map:</p>
<ul>
<li>geojson for gps tracks of hiking</li>
<li>for every photo there is a marker with a popup containing a thumbnail and a link to the corresponding photo gallery image</li>
</ul>
<p>Also for every photo gallery page I add a PhotoSwipe gallery containing some <a href="https://photoswipe.com/adding-ui-elements/" target="_blank" rel="noreferrer">custom ui html elements</a>: an svg icon referring the photo marker within the map and a custom <code>onClick()</code> function.</p>
<p>These components can work together thanks to a global state handled by <code>pinia</code>. First I defined the pinia store:</p>
<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// .vitepress/store/stores.ts</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { defineStore } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "pinia"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> mapStore</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> defineStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'map-store'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, {</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            closePopup: Boolean,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            markers: [],</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            selectedPopupCoordinate: Array,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            selectedPopupId: Number,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            selectedPopupIdWithCoordinates: Number,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            selectedImageIndex: Number,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">})</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { mapStore }</span></span></code></pre>
</div><p>I use two Vue components: <code>GalleryComponent.vue</code> and <code>MapComponent.vue</code>.</p>
<h2 id="how-to-open-the-correct-photo-within-the-photo-gallery-clicking-on-a-marker-popup" tabindex="-1">How to open the correct photo within the photo gallery clicking on a marker popup <a class="header-anchor" href="#how-to-open-the-correct-photo-within-the-photo-gallery-clicking-on-a-marker-popup" aria-label="Permalink to &quot;How to open the correct photo within the photo gallery clicking on a marker popup&quot;"></a></h2>
<p>Every popup marker contains a link that, on click, patches the <code>pinia</code> state with the selected photo ID (and the corresponding marker ID). The tricky part was connecting this function to the <code>a</code> HTML popup element, so I created a wrapper function <code>getPopup()</code> that before creates the HTML elements with the correct <code>onClick()</code> function I already talked about (lines 17-23):</p>
<div class="language-ts vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// MapComponent.vue</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// define the pinia state store</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (inBrowser </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> localStore </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    localStore </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> photoStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getPopup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">id</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">titleContent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">urlthumb</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> HTMLDivElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // manually build html elements to set global pinia state from here</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> title</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> HTMLSpanElement</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> document.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">createElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"span"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  title.innerHTML </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> `${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">titleContent</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}`</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> a</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> HTMLAnchorElement</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> document.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">createElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"a"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  a.id </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> `popup-a-${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">id</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}`</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // this action opens the selected photo within the photo gallery and close this marker popup</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  a.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">onClick</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> eventClick</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">event</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    event.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">preventDefault</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    localStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">$patch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      selectedImageIndex: id,</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      closePopup: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    })</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  a.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">appendChild</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(title)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> div</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> HTMLDivElement</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> document.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">createElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"div"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  div.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">appendChild</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(a)</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> div</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br></div></div><p>Of course the Vue component should contains also a way the read the updated store and here I use the <code>selectedImageIndex</code> variable to open the selected image within the photo gallery:</p>
<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// GalleryComponent.vue</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> localMapStore;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (inBrowser </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> localMapStore </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  localMapStore </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> mapStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  localMapStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">$subscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">mutation</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">payload</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> mutation;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">selectedImageIndex</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> payload;</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // open the selected photo within the photo gallery</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (selectedImageIndex </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> undefined</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        handleGalleryOpen</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(selectedImageIndex)</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  })</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> handleGalleryOpen</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">index</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // from https://github.com/hzpeng57/vue-preview-imgs/blob/master/packages/example/src/App.vue</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  lightbox.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">loadAndOpen</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">parseInt</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(index, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">10</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span></code></pre>
</div><h2 id="going-backwards-from-a-photo-within-the-photo-gallery-to-the-corresponding-popup-marker" tabindex="-1">Going backwards: from a photo within the photo gallery to the corresponding popup marker <a class="header-anchor" href="#going-backwards-from-a-photo-within-the-photo-gallery-to-the-corresponding-popup-marker" aria-label="Permalink to &quot;Going backwards: from a photo within the photo gallery to the corresponding popup marker&quot;"></a></h2>
<p>When a user clicks the link inside the popup marker to open the corresponding photo, the sequence of actions is simple: first clicking the popup link patches the <code>pinia</code> state, then the store instance uses the method <code>.$subscribe({...})</code> to open the selected photo in the photo gallery. Easy.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/B5oO0PT_-Ao?si=B62c9c5Efi8idMRz&amp;start=50" title="Going Backwards" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<p>The reverse process, however, is more complicated: the <code>leaflet</code> map can't show a marker if before it hasn't set the map view using the correct marker coordinates, but the photo gallery knows only about the current image index itself (lines 24-29):</p>
<div class="language-ts vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">onMounted</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> galleryDiv</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> HTMLElement</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> document.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getElementById</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">`gallery-photo-${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">props</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">.</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">galleryID</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}`</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> galleryChildren</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> HTMLCollection</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> undefined</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> galleryDiv?.children</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> dataSource</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    gallery: galleryDiv,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    items: galleryChildren</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> options</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    gallery: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">`#gallery-photo-${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">props</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">.</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">galleryID</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}`</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    children: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'a'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    pswpModule</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'photoswipe'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">),</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    dataSource: dataSource </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// fix missing gallery on first load with custom lightbox.loadAndOpen() action</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (lightbox </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> PhotoSwipeLightbox</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({})) {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    lightbox </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> PhotoSwipeLightbox</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(options);</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    lightbox.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">on</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'uiRegister'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      lightbox.pswp.ui.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">registerElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        name: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'location-button'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        order: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">8</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        isButton: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        tagName: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'a'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        html: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'&#x3C;svg code ... />'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">        // onClick function for the custom position button within GalleryComponent.vue, onMount() hook</span></span>
<span class="line highlighted"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        onClick</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">event</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">el</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">pswp</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">          localMapStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">$patch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            selectedPopupIdFromGallery: </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">parseInt</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(pswp.currSlide.index, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">10</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">          })</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">          pswp.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">close</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      });</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    });</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    lightbox.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">init</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">})</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br></div></div><p>Note on line 12: that's a workaround needed to avoid the gallery has missing content on the first load when using a custom <code>lightbox.loadAndOpen()</code> method.</p>
<p>Because of the missing marker coordinates during the former patch store action I added an intermediate step where I filter the markers I already put within the store to extract the selected marker coordinates:</p>
<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// GalleryComponent.vue</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { LatLngTuple } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "leaflet"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// ...</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> localMapStore;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (inBrowser </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> localMapStore </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  localMapStore </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> mapStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  localMapStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">$subscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">mutation</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">payload</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> mutation;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">selectedPopupIdFromGallery</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> payload;</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // filter the markers to select the marker coordinates used to set the marker map view</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {markers} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> state;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> selectedMarkers</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> markers[props.galleryID]</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (selectedPopupIdFromGallery </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> undefined</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> &#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> selectedMarkers </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> undefined</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">      // m.id: number type!</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> filteredMarker </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> selectedMarkers.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">find</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">m</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> m.id </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">==</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> selectedPopupIdFromGallery)</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> coordinate</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> LatLngTuple</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> filteredMarker.coordinate</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      localMapStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">$patch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        selectedPopupCoordinate: coordinate,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        selectedPopupIdWithCoordinates: filteredMarker.id</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      })</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  })</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
</div><p>Note that in this case the objects within the <code>markers</code> array contain at least <code>id</code> (a number variable) and <code>coordinate</code> (LatLngTuple type from <code>leaflet</code>).
Next phase should use the store payload content (<code>selectedPopupIdWithCoordinates</code> and <code>selectedPopupCoordinate</code>) to set the current map view where the selected marker is and then to open his popup:</p>
<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// MapComponent.vue</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> zoomValue</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 18</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> popupContent</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getPopup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> popup </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> L</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">popup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(m.coordinate).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setContent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(popupContent)</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> marker</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> L</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">marker</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(coordinate, {</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bindPopup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(popup);</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // here add the current marker to the marker cluster instance...</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  localMapStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">$subscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">mutation</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">payload</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> mutation;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">closePopup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">selectedPopupIdWithCoordinates</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">selectedPopupCoordinate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> payload;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (selectedPopupIdWithCoordinates </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">==</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> m.idx </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> selectedPopupCoordinate) {</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">      // m.id: number type!</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      map.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setView</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(selectedPopupCoordinate, zoomValue)</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      marker.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">openPopup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (closePopup) {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      marker.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">closePopup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  })</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // ...</span></span></code></pre>
</div><p>And... <em>that's the way you do it</em>!</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/wTP2RUD_cL0?si=9E_yHgJbr6Kpv3SE&amp;start=65" title="Money for Nothing" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Vitepress and leaflet.markercluster]]></title>
            <link>/blog/vitepress--leaflet-markercluster</link>
            <guid isPermaLink="false">/blog/vitepress--leaflet-markercluster</guid>
            <pubDate>Tue, 05 Sep 2023 00:50:56 GMT</pubDate>
            <description><![CDATA[Advises about software integration of Vitepress and Leaflet Markercluster]]></description>
            <content:encoded><![CDATA[<h1 id="vitepress-and-leaflet-markercluster" tabindex="-1">Vitepress and leaflet.markercluster <a class="header-anchor" href="#vitepress-and-leaflet-markercluster" aria-label="Permalink to &quot;Vitepress and leaflet.markercluster&quot;"></a></h1>
<p><a href="https://vitepress.dev/guide/ssr-compat" target="_blank" rel="noreferrer">VitePress pre-renders the app in Node.js during the production build, using Vue's Server-Side Rendering (SSR) capabilities</a>: this fact prevents using 'leaflet.markercluster' (or its wrapper module <a href="https://github.com/veitbjarsch/vue-leaflet-markercluster" target="_blank" rel="noreferrer">'vue-leaflet-markercluster'</a>) within a vitepress project. Let's say we create this simple Leaflet map within a Vue component imported within a vitepress project:</p>
<div class="language-vue vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">vue</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">template</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> id</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"map"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">l-map</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">zoom</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">center</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">45.1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">use-global-leaflet</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    ></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">l-tile-layer</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> url</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">attribution</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">attribution</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">/></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">l-marker-cluster-group</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">l-marker</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">lat-lng</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">45.1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">/></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">l-marker</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">lat-lng</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">45.2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">/></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">l-marker</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">lat-lng</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">45.3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">/></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">l-marker-cluster-group</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">l-map</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">template</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">script</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> setup</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> lang</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"ts"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {LMarker, LMap, LTileLayer} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "@vue-leaflet/vue-leaflet"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> L, {marker} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'leaflet'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">globalThis.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">L</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> L</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {LMarkerClusterGroup} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'vue-leaflet-markercluster'</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {ref} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'vue'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'leaflet/dist/leaflet.css'</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'vue-leaflet-markercluster/dist/style.css'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> attribution</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ref</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"openstreetmap contributors"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> prefix</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ref</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"leaflet"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">script</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">style</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> scoped</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#map</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  height</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">50</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">vh</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  width</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">100</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">%</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">style</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span></code></pre>
</div><p>While running this code with <code>yarn docs:dev</code> or <code>pnpm docs:dev</code> (aliases of <code>vitepress dev docs</code>, see the <a href="https://vitepress.dev/guide/getting-started#up-and-running" target="_blank" rel="noreferrer">vitepress documentation</a>) it's fine, of course the <code>leaflet.markercluster</code> SSR requirements break the build step. Luckily it's possible to build the project using the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import" target="_blank" rel="noreferrer"><code>import()</code> syntax, commonly called dynamic import</a>. There are different possible implementations (e.g. <a href="https://vuejs.org/guide/components/async.html" target="_blank" rel="noreferrer">async components</a>) but in this case I prefer using the classic JS syntax within the <code>onBeforeMount()</code> hook, then we can fix the broken component this way:</p>
<div class="language-vue vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">vue</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">template</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> id</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"map-photos"</span><span style="--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;--shiki-light:#B31D28;--shiki-light-font-style:italic">/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">template</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">script</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> setup</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> lang</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"ts"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "leaflet/dist/leaflet.css"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "leaflet.markercluster/dist/MarkerCluster.css"</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "leaflet.markercluster/dist/MarkerCluster.Default.css"</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {onBeforeMount} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "vue"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">onBeforeMount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">async</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> L</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> await</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"leaflet"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">MarkerClusterGroup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> await</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'leaflet.markercluster/dist/leaflet.markercluster'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> map</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> L</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">map</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"map-photos"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// div id</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  map.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setView</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">45.1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">], </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">8</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  L</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">tileLayer</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"http://{s}.tile.osm.org/{z}/{x}/{y}.png"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">addTo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(map);</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> mcluster </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MarkerClusterGroup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> marker1 </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> L</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Marker</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">45.1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">])</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> marker2 </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> L</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Marker</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">45.2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">])</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> marker3 </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> L</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Marker</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">45.3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">])</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  marker1.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">addTo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(mcluster)</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  marker2.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">addTo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(mcluster)</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  marker3.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">addTo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(mcluster)</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  mcluster.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">addTo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(map)</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">})</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">script</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">style</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> scoped</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#map-photos</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  width</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">100</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">%</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  height</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">60</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">vh</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">style</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span></code></pre>
</div><p>Note that <code>onBeforeMount()</code> must be async to make it work.
Last but not least the <code>package.json</code> of this vitepress project contains these dependencies:</p>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "name"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"vitepress-ssr-build"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "version"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"1.0.0"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "scripts"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "docs:dev"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"vitepress dev docs"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "docs:build"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"pnpm i --frozen-lockfile &#x26;&#x26; vitepress build docs"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "docs:serve"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"vitepress serve docs"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "docs:preview"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"vitepress preview docs --port 3000"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "license"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"MIT"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"module"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "dependencies"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "@vue-leaflet/vue-leaflet"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^0.10.1"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "leaflet"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^1.9.4"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "leaflet.markercluster"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^1.5.3"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "vitepress"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^1.0.0-rc.10"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "vue"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^3.3.4"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "devDependencies"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "@tsconfig/recommended"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^1.0.2"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "@types/geojson"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^7946.0.10"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "@types/leaflet"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^1.9.4"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "@types/leaflet.markercluster"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^1.5.2"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
</div>]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Da un marker leaflet alla foto in una galleria fotografica e ritorno]]></title>
            <link>/it/blog/from-leaflet-popup-marker-to-photogallery-and-back</link>
            <guid isPermaLink="false">/it/blog/from-leaflet-popup-marker-to-photogallery-and-back</guid>
            <pubDate>Tue, 05 Sep 2023 00:50:56 GMT</pubDate>
            <description><![CDATA[Raccomandazioni per l'integrazione di Photswipe, Vitepress e Leaflet]]></description>
            <content:encoded><![CDATA[<h1 id="da-un-marker-leaflet-alla-foto-in-una-galleria-fotografica-e-ritorno" tabindex="-1">Da un marker leaflet alla foto in una galleria fotografica e ritorno <a class="header-anchor" href="#da-un-marker-leaflet-alla-foto-in-una-galleria-fotografica-e-ritorno" aria-label="Permalink to &quot;Da un marker leaflet alla foto in una galleria fotografica e ritorno&quot;"></a></h1>
<p>Quando vado a fare escursioni spesso scatto foto dei dintorni. Per questo motivo cerco di registrare il percorso delle mie passeggiate insieme ai segnalibri con la posizione delle foto.</p>
<p>Al momento uso <a href="https://leafletjs.com/" target="_blank" rel="noreferrer">Leaflet</a> per gestire le mappe geografiche e <a href="https://photoswipe.com/" target="_blank" rel="noreferrer">PhotoSwipe</a> per le gallerie fotografiche. Dal punto di vista tecnico visualizzo sulla mappa:</p>
<ul>
<li>i geojson contenenti le tracce gps delle escursioni</li>
<li>per ogni foto i segnalibri con le coordinate delle foto a cui è connesso un popup contenente una thumbnail della foto ed un collegamento all'immagine nella photogallery corrispondente</li>
</ul>
<p>Inoltre in questo caso personalizzo la galleria <code>photoswipe</code> aggiungendo alcuni <a href="https://photoswipe.com/adding-ui-elements/" target="_blank" rel="noreferrer">elementi html custom dell'interfaccia grafica</a> (un'icona SVG che rimanda al segnalibro della foto all'interno della mappa con una funzione <code>onClick()</code> custom).</p>
<p>Questi componenti possono lavorare insieme grazie ad uno stato globale gestito da <code>pinia</code>. Per prima cosa ho creato lo store contenente lo stato globale:</p>
<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// .vitepress/store/stores.ts</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { defineStore } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "pinia"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> mapStore</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> defineStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'map-store'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, {</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            closePopup: Boolean,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            markers: [],</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            selectedPopupCoordinate: Array,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            selectedPopupId: Number,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            selectedPopupIdWithCoordinates: Number,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            selectedImageIndex: Number,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">})</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { mapStore }</span></span></code></pre>
</div><p>Definiamo inoltre due componenti vue: <code>GalleryComponent.vue</code> e <code>MapComponent.vue</code>.</p>
<h2 id="_1-come-aprire-la-foto-selezionata-all-interno-della-galleria-fotografica-facendo-clic-sul-popup-del-marker-leaflet" tabindex="-1">1. Come aprire la foto selezionata all'interno della galleria fotografica facendo clic sul popup del marker leaflet <a class="header-anchor" href="#_1-come-aprire-la-foto-selezionata-all-interno-della-galleria-fotografica-facendo-clic-sul-popup-del-marker-leaflet" aria-label="Permalink to &quot;1. Come aprire la foto selezionata all'interno della galleria fotografica facendo clic sul popup del marker leaflet&quot;"></a></h2>
<p>Ogni popup leaflet contiene un collegamento che, al clic, aggiorna lo store usando l'ID della foto selezionata (ovvero l'ID del marker corrispondente). La parte difficile è stata connettere la funzione che aggiorna lo store al HTMLAnchorElement <code>a</code> contenuto nel popup. Per superare l'ostacolo ho creato una funzione wrapper <code>getPopup()</code> che prima crea gli elementi HTML necessari, fra cui anche l'elemento HTML <code>a</code> con il metodo custom <code>onClick()</code> di cui sopra (righe 17-23):</p>
<div class="language-ts vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// MapComponent.vue</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// define the pinia state store</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (inBrowser </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> localStore </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    localStore </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> photoStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getPopup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">id</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">titleContent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">urlthumb</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> HTMLDivElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // manually build html elements to set global pinia state from here</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> title</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> HTMLSpanElement</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> document.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">createElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"span"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  title.innerHTML </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> `${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">titleContent</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}`</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> a</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> HTMLAnchorElement</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> document.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">createElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"a"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  a.id </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> `popup-a-${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">id</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}`</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // this action opens the selected photo within the photo gallery and close this marker popup</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  a.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">onClick</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> eventClick</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">event</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    event.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">preventDefault</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    localStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">$patch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      selectedImageIndex: id,</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      closePopup: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    })</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  a.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">appendChild</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(title)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> div</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> HTMLDivElement</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> document.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">createElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"div"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  div.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">appendChild</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(a)</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> div</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br></div></div><p>Ovviamente il componente Vue dovrebbe contenere anche un modo per leggere lo store aggiornato da cui estrarre la variabile <code>selectedImageIndex</code> con cui aprire poi l'immagine nella galleria fotografica:</p>
<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// GalleryComponent.vue</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> localMapStore;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (inBrowser </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> localMapStore </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  localMapStore </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> mapStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  localMapStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">$subscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">mutation</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">payload</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> mutation;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">selectedImageIndex</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> payload;</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // open the selected photo within the photo gallery</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (selectedImageIndex </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> undefined</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        handleGalleryOpen</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(selectedImageIndex)</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  })</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> handleGalleryOpen</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">index</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // from https://github.com/hzpeng57/vue-preview-imgs/blob/master/packages/example/src/App.vue</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  lightbox.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">loadAndOpen</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">parseInt</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(index, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">10</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span></code></pre>
</div><h2 id="_2-andando-indietro-da-una-foto-all-interno-della-galleria-fotografica-al-corrispondente-indicatore-popup" tabindex="-1">2. Andando indietro: da una foto all'interno della galleria fotografica al corrispondente indicatore popup <a class="header-anchor" href="#_2-andando-indietro-da-una-foto-all-interno-della-galleria-fotografica-al-corrispondente-indicatore-popup" aria-label="Permalink to &quot;2. Andando indietro: da una foto all'interno della galleria fotografica al corrispondente indicatore popup&quot;"></a></h2>
<p>Quando un utente fa clic sul collegamento all'interno del popup per aprire la foto corrispondente, la sequenza di azioni è semplice: prima facendo clic sul collegamento nel popup si aggiorna lo store <code>pinia</code>. In seguito l'istanza dello store con il metodo <code>.$subscribe({...})</code> apre la foto selezionata all'interno della galleria fotografica. Facile.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/B5oO0PT_-Ao?si=B62c9c5Efi8idMRz&amp;start=50" title="Going Backwards" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<p>Il processo inverso, però, è più complicato: la mappa <code>leaflet</code> non può aprire il popup senza aver preventivamente impostato la view corrispondente alle coordinate del marker selezionato. La galleria fotografica però ha soltanto il dato relativo al ID dell'immagine stessa attualmente aperta (righe 24-29):</p>
<div class="language-ts vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">onMounted</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> galleryDiv</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> HTMLElement</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> document.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getElementById</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">`gallery-photo-${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">props</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">.</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">galleryID</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}`</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> galleryChildren</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> HTMLCollection</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> undefined</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> galleryDiv?.children</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> dataSource</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    gallery: galleryDiv,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    items: galleryChildren</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> options</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    gallery: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">`#gallery-photo-${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">props</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">.</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">galleryID</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}`</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    children: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'a'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    pswpModule</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'photoswipe'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">),</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    dataSource: dataSource </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// fix missing gallery on first load with custom lightbox.loadAndOpen() action</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (lightbox </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> PhotoSwipeLightbox</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({})) {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    lightbox </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> PhotoSwipeLightbox</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(options);</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    lightbox.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">on</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'uiRegister'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      lightbox.pswp.ui.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">registerElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        name: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'location-button'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        order: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">8</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        isButton: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        tagName: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'a'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        html: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'&#x3C;svg code ... />'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">        // onClick function for the custom position button within GalleryComponent.vue, onMount() hook</span></span>
<span class="line highlighted"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        onClick</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">event</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">el</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">pswp</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">          localMapStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">$patch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            selectedPopupIdFromGallery: </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">parseInt</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(pswp.currSlide.index, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">10</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">          })</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">          pswp.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">close</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span></span>
<span class="line highlighted"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      });</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    });</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    lightbox.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">init</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">})</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br></div></div><p>Notare la riga 12: si tratta di un workaround necessario per evitare che il contenuto della galleria sia mancante al primo caricamento custom (in questo caso usando <code>lightbox.loadAndOpen()</code>) della galleria.</p>
<p>A causa delle coordinate mancanti del marker nella fase precedente ho aggiunto un passaggio intermedio si filtrano i marker estratti dallo store per ricavare le coordinate del marker selezionato:</p>
<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// GalleryComponent.vue</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { LatLngTuple } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "leaflet"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// ...</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> localMapStore;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (inBrowser </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> localMapStore </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  localMapStore </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> mapStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  localMapStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">$subscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">mutation</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">payload</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> mutation;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">selectedPopupIdFromGallery</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> payload;</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // filter the markers to select the marker coordinates used to set the marker map view</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {markers} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> state;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> selectedMarkers</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> markers[props.galleryID]</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (selectedPopupIdFromGallery </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> undefined</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> &#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> selectedMarkers </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> undefined</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">      // m.id: number type!</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> filteredMarker </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> selectedMarkers.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">find</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">m</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> m.id </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">==</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> selectedPopupIdFromGallery)</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> coordinate</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> LatLngTuple</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> filteredMarker.coordinate</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      localMapStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">$patch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        selectedPopupCoordinate: coordinate,</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        selectedPopupIdWithCoordinates: filteredMarker.id</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      })</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  })</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
</div><p>Si noti che in questo caso gli oggetti dentro all'array <code>markers</code> conterranno almeno <code>id</code> (tipo numero) e <code>coordinate</code> (<code>LatLngTuple</code> dalla libreria <code>leaflet</code>).
La fase successiva usa il contenuto del payload dello store (<code>selectedPopupIdWithCoordinates</code> e <code>selectedPopupCoordinate</code> in particolare) per impostare la visualizzazione corrente della mappa in cui si trova il segnalibro selezionato ed aprirne quindi finalmente il popup:</p>
<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// MapComponent.vue</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> zoomValue</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 18</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> popupContent</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getPopup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> popup </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> L</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">popup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(m.coordinate).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setContent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(popupContent)</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> marker</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> L</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">marker</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(coordinate, {</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bindPopup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(popup);</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // here add the current marker to the marker cluster instance...</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  localMapStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">$subscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">mutation</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">payload</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> mutation;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">closePopup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">selectedPopupIdWithCoordinates</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">selectedPopupCoordinate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> payload;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (selectedPopupIdWithCoordinates </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">==</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> m.idx </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> selectedPopupCoordinate) {</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">      // m.id: number type!</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      map.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setView</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(selectedPopupCoordinate, zoomValue)</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      marker.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">openPopup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (closePopup) {</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      marker.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">closePopup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  })</span></span>
<span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // ...</span></span></code></pre>
</div><p>E... <em>that's the way you do it</em>!</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/wTP2RUD_cL0?si=9E_yHgJbr6Kpv3SE&amp;start=65" title="Money for Nothing" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[vitepress e leaflet.markercluster]]></title>
            <link>/it/blog/vitepress--leaflet-markercluster</link>
            <guid isPermaLink="false">/it/blog/vitepress--leaflet-markercluster</guid>
            <pubDate>Tue, 05 Sep 2023 00:50:56 GMT</pubDate>
            <description><![CDATA[Raccomandazioni per l'integrazione software di Vitepress e Leaflet Markercluster]]></description>
            <content:encoded><![CDATA[<h1 id="vitepress-e-leaflet-markercluster" tabindex="-1">Vitepress e leaflet.markercluster <a class="header-anchor" href="#vitepress-e-leaflet-markercluster" aria-label="Permalink to &quot;Vitepress e leaflet.markercluster&quot;"></a></h1>
<p><a href="https://vitepress.dev/guide/ssr-compat" target="_blank" rel="noreferrer">VitePress esegue il pre-rendering dell'app in Node.js durante la build di produzione, utilizzando le funzionalità di rendering lato server (SSR) di Vue</a>: questo fatto impedisce di usare <code>leaflet.markercluster</code> all'interno di un progetto vitepress. Supponiamo di creare questa semplice mappa leaflet all'interno di un componente vue a sua volta utilizzato in un progetto vitepress:</p>
<div class="language-vue vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">vue</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">template</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> id</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"map"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">l-map</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">zoom</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">center</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">45.1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">use-global-leaflet</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    ></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">l-tile-layer</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> url</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">attribution</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">attribution</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">/></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">l-marker-cluster-group</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">l-marker</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">lat-lng</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">45.1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">/></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">l-marker</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">lat-lng</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">45.2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">/></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">l-marker</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">lat-lng</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">45.3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">/></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">l-marker-cluster-group</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">l-map</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">template</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">script</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> setup</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> lang</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"ts"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {LMarker, LMap, LTileLayer} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "@vue-leaflet/vue-leaflet"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> L, {marker} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'leaflet'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">globalThis.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">L</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> L</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {LMarkerClusterGroup} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'vue-leaflet-markercluster'</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {ref} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'vue'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'leaflet/dist/leaflet.css'</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'vue-leaflet-markercluster/dist/style.css'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> attribution</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ref</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"openstreetmap contributors"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> prefix</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ref</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"leaflet"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">script</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">style</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> scoped</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#map</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  height</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">50</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">vh</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  width</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">100</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">%</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">style</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span></code></pre>
</div><p>Mentre <code>yarn docs:dev</code> o <code>pnpm docs:dev</code> (alias di <code>vitepress dev docs</code>, si veda la <a href="https://vitepress.dev/guide/getting-started#up-and-running" target="_blank" rel="noreferrer">documentazione vitepress</a>) eseguono con successo il codice qui sopra, la fase di build fallisce a causa appunto dell'utilizzo di SSR da parte di <code>leaflet.markercluster</code>.
Fortunatamente è possibile evitare il problema utilizzando la sintassi <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import" target="_blank" rel="noreferrer"><code>import()</code>, comunemente definitita &quot;import dinamico&quot;</a>. Esistono diverse implementazioni possibili (ad es. <a href="https://vuejs.org/guide/components/async.html" target="_blank" rel="noreferrer">async components</a>) ma in questo caso preferisco utilizzare la classica sintassi javascript all'interno dell'hook <code>onBeforeMount()</code>. Si può quindi eliminare il malfunzionamento in questo modo:</p>
<div class="language-vue vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">vue</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">template</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> id</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"map-photos"</span><span style="--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;--shiki-light:#B31D28;--shiki-light-font-style:italic">/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">template</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">script</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> setup</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> lang</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"ts"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "leaflet/dist/leaflet.css"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "leaflet.markercluster/dist/MarkerCluster.css"</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "leaflet.markercluster/dist/MarkerCluster.Default.css"</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {onBeforeMount} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "vue"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">onBeforeMount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">async</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> L</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> await</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"leaflet"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">MarkerClusterGroup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> await</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'leaflet.markercluster/dist/leaflet.markercluster'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> map</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> L</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">map</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"map-photos"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// div id</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  map.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setView</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">45.1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">], </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">8</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  L</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">tileLayer</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"http://{s}.tile.osm.org/{z}/{x}/{y}.png"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">addTo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(map);</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> mcluster </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MarkerClusterGroup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> marker1 </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> L</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Marker</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">45.1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">])</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> marker2 </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> L</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Marker</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">45.2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">])</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> marker3 </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> L</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Marker</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">45.3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9.3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">])</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  marker1.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">addTo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(mcluster)</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  marker2.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">addTo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(mcluster)</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  marker3.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">addTo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(mcluster)</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  mcluster.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">addTo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(map)</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">})</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">script</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">style</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> scoped</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#map-photos</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  width</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">100</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">%</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  height</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">60</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">vh</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">style</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span></code></pre>
</div><p>Si noti che <code>onBeforeMount()</code>  deve esser asincrono per funzionare.
Ultimo ma non per importanza il package.json di questo progetto vitepress minimale deve contenere queste dipendenze:</p>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "name"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"vitepress-ssr-build"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "version"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"1.0.0"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "scripts"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "docs:dev"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"vitepress dev docs"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "docs:build"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"pnpm i --frozen-lockfile &#x26;&#x26; vitepress build docs"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "docs:serve"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"vitepress serve docs"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "docs:preview"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"vitepress preview docs --port 3000"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "license"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"MIT"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"module"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "dependencies"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "@vue-leaflet/vue-leaflet"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^0.10.1"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "leaflet"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^1.9.4"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "leaflet.markercluster"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^1.5.3"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "vitepress"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^1.0.0-rc.10"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "vue"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^3.3.4"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "devDependencies"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "@tsconfig/recommended"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^1.0.2"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "@types/geojson"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^7946.0.10"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "@types/leaflet"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^1.9.4"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "@types/leaflet.markercluster"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"^1.5.2"</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
</div>]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Una passeggiata nei dintorni di Bormio]]></title>
            <link>/it/photo-galleries/bormio-20230826</link>
            <guid isPermaLink="false">/it/photo-galleries/bormio-20230826</guid>
            <pubDate>Sat, 26 Aug 2023 10:35:01 GMT</pubDate>
            <description><![CDATA[Una camminata fra boschi alpini e prati rigogliosi a Bormio]]></description>
            <content:encoded><![CDATA[<h1 id="bormio-26-agosto-2023" tabindex="-1">Bormio, 26 Agosto 2023 <a class="header-anchor" href="#bormio-26-agosto-2023" aria-label="Permalink to &quot;Bormio, 26 Agosto 2023&quot;"></a></h1>
<h2 id="una-passeggiata-nei-dintorni-di-bormio" tabindex="-1">Una passeggiata nei dintorni di Bormio <a class="header-anchor" href="#una-passeggiata-nei-dintorni-di-bormio" aria-label="Permalink to &quot;Una passeggiata nei dintorni di Bormio&quot;"></a></h2>
<div id="map-container">
    <MapWithPolyline
      :center='[46.469248, 10.3805]'
      location="Bormio"
      :mapName="mapName"
      :pathUrl="`/paths/${mapName}.json`"
      :photosUrl="`/photos/${mapName}.json`"
      zoom=14
    />
</div>
<h2 id="trentadue-viste-di-bormio-e-dintorni" tabindex="-1">Trentadue viste di Bormio e dintorni <a class="header-anchor" href="#trentadue-viste-di-bormio-e-dintorni" aria-label="Permalink to &quot;Trentadue viste di Bormio e dintorni&quot;"></a></h2>
<p>Foto scattate il 26 agosto 2023.</p>
<div id="image-gallery-container">
    <SimpleGallery
      :galleryID="mapName"
      :imagesData="imagesData"
    />
</div>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[A walk around Bormio]]></title>
            <link>/photo-galleries/bormio-20230826</link>
            <guid isPermaLink="false">/photo-galleries/bormio-20230826</guid>
            <pubDate>Sat, 26 Aug 2023 10:35:00 GMT</pubDate>
            <description><![CDATA[A walk through alpine woods and lush meadows in Bormio]]></description>
            <content:encoded><![CDATA[<h1 id="bormio-26-august-2023" tabindex="-1">Bormio, 26 August 2023 <a class="header-anchor" href="#bormio-26-august-2023" aria-label="Permalink to &quot;Bormio, 26 August 2023&quot;"></a></h1>
<h2 id="a-walk-around-bormio" tabindex="-1">A walk around Bormio <a class="header-anchor" href="#a-walk-around-bormio" aria-label="Permalink to &quot;A walk around Bormio&quot;"></a></h2>
<div id="map-container">
    <MapWithPolyline
      :center='[46.469248, 10.3805]'
      location="Bormio"
      :mapName="mapName"
      :pathUrl="`/paths/${mapName}.json`"
      :photosUrl="`/photos/${mapName}.json`"
      zoom=14
    />
</div>
<h2 id="thirty-two-views-of-bormio-and-surroundings" tabindex="-1">Thirty-two views of Bormio and surroundings <a class="header-anchor" href="#thirty-two-views-of-bormio-and-surroundings" aria-label="Permalink to &quot;Thirty-two views of Bormio and surroundings&quot;"></a></h2>
<p>Photos taken on 26 August 2023.</p>
<div id="image-gallery-container">
    <SimpleGallery
      :galleryID="mapName"
      :imagesData="imagesData"
    />
</div>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[TwentyOneSeconds - a CV LaTeX template]]></title>
            <link>/blog/latex-cv</link>
            <guid isPermaLink="false">/blog/latex-cv</guid>
            <pubDate>Wed, 23 Aug 2023 18:11:32 GMT</pubDate>
            <description><![CDATA[Write a professional CV using this multi-page two-column LaTeX template]]></description>
            <content:encoded><![CDATA[<h1 id="twentyoneseconds-a-cv-latex-template" tabindex="-1">TwentyOneSeconds - a CV LaTeX template <a class="header-anchor" href="#twentyoneseconds-a-cv-latex-template" aria-label="Permalink to &quot;TwentyOneSeconds - a CV LaTeX template&quot;"></a></h1>
<p>I started from the <a href="https://github.com/spagnuolocarmine/TwentySecondsCurriculumVitae-LaTex" target="_blank" rel="noreferrer">twentyseconds</a> LaTeX theme
because I think <a href="https://www.quora.com/Is-building-a-resume-in-LaTeX-a-good-idea" target="_blank" rel="noreferrer">a LaTeX CV looks more professional</a>. It's also possible to do things <a href="https://xkcd.com/1676/" target="_blank" rel="noreferrer">like this</a>:</p>
<p><img src="https://imgs.xkcd.com/comics/full_width_justification.png" alt="a-bad-justification" title="A bad justification"></p>
<p>I like the two-column layout but not the left column created outside the document.
Because of this I should set the dimensions of the second column exactly like those of the frame containing the text, making the use of LaTeX useless.</p>
<p>To replicate this two-column configuration I thought to use this clever <a href="https://tex.stackexchange.com/a/310989/109031" target="_blank" rel="noreferrer">solution</a> based on the <code>tcolorbox</code> package.</p>
<p>First of all I created this basic LaTeX document:</p>
<div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-1HY_N" id="tab-fb9MNFM" checked><label data-title="twentyonesecondcv.cls" for="tab-fb9MNFM">twentyonesecondcv.cls</label><input type="radio" name="group-1HY_N" id="tab-uCs8VF5" ><label data-title="cv.tex" for="tab-uCs8VF5">cv.tex</label></div><div class="blocks">
<div class="language-latex vp-adaptive-theme active"><button title="Copy Code" class="copy"></button><span class="lang">latex</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\ProvidesClass</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{twentyonesecondcv}[2017/01/08 CV class]</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\documentclass</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">article</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\usepackage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">blindtext</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\usepackage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">xcolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\usepackage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">geometry</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\usepackage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[most]{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">tcolorbox</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\geometry</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{margin=0pt}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\definecolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{gray}{HTML}{4D4D4D}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\definecolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{sidecolor}{HTML}{E7E7E7}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\definecolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{mainblue}{HTML}{0E5484}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\definecolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{maingray}{HTML}{B9B9B9}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\newtcolorbox</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}[1][]{nobeforeafter,leftright skip=0pt,boxrule=0pt,enhanced jigsaw,sharp corners,#1}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\newcommand</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\sidesection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}[1]{</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \noindent</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \begin</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}[height=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\paperheight</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,colback=sidecolor,width=0.33</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\textwidth</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    #1</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \end</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">%</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\newcommand</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\mainsection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}[1]{</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">%</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \noindent</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \begin</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}[height=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\paperheight</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,colback=white,width=0.67</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\textwidth</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    #1</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \end</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">%</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
</div><div class="language-latex vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">latex</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\documentclass</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[letterpaper]{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">twentyonesecondcv</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">% a4paper for A4</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\begin</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{document}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\noindent</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\sidesection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{col 1, pag 1:: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\lipsum</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[1-3]}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\mainsection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{col 2, pag 1:: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\textit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-dark-font-style:italic;--shiki-light:#005CC5;--shiki-light-font-style:italic">\lipsum</span><span style="--shiki-dark:#E1E4E8;--shiki-dark-font-style:italic;--shiki-light:#24292E;--shiki-light-font-style:italic">[1-5]</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\newpage</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\noindent</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\sidesection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{col 1, pag2:: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\lipsum</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[3-5]}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\mainsection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{col 2, pag2:: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\textit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-dark-font-style:italic;--shiki-light:#005CC5;--shiki-light-font-style:italic">\lipsum</span><span style="--shiki-dark:#E1E4E8;--shiki-dark-font-style:italic;--shiki-light:#24292E;--shiki-light-font-style:italic">[3-7]</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\end</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{document}</span></span></code></pre>
</div></div></div>
<p>You can notice that this example is already on two pages: it's ok to keep your CV simple and quick to read, but what if you have a long list of experiences to show?</p>
<p>Of course LaTeX is good because the markup engine should keep you free to think about your content, not concentrating on handling manual placements of borders, paragraph positions and so on. My biggest problem about this was filling empty space: commands like <code>\vfill</code> aren't working within <code>newtcolorbox</code> because the box has no fixed height.</p>
<p>Luckily (should be from version 3.70) it's possible to change this behavior; for this you need the options</p>
<ul>
<li><code>height=&lt;value&gt;</code></li>
<li><code>text fill</code></li>
</ul>
<p>As already said, the box can't use <code>\vfill</code> because initially it hasn't a fixed height, so these options are changing that. I use <code>height=\heigthpage</code> because my boxes' height are equal to the entire page height.</p>
<p>Last precaution is to avoid commands enforcing fixed value settings like the <code>tikzpicture</code> one used within the custom command <code>\makeheaderprofile</code>.</p>
<p>For some reason instead the custom commands used to create the skill sections (\skills and \skillstext) work fine.</p>
<p>Finally <a href="/trinca_public.pdf">here</a> is the result and the <a href="https://github.com/trincadev/cv-latex-twentyoneseconds" target="_blank" rel="noreferrer">GitHub repository</a>!</p>
<p>I published this LaTeX template on this <a href="https://www.overleaf.com/latex/templates/twentyoneseconds/xmvbqtfmnycf" target="_blank" rel="noreferrer">Overleaf page</a>.</p>
<h2 id="updates" tabindex="-1">Updates <a class="header-anchor" href="#updates" aria-label="Permalink to &quot;Updates&quot;"></a></h2>
<h3 id="_2024-06-26" tabindex="-1">2024-06-26 <a class="header-anchor" href="#_2024-06-26" aria-label="Permalink to &quot;2024-06-26&quot;"></a></h3>
<ul>
<li>Add LICENSE file</li>
<li>add alternative commands to create the side footer text</li>
</ul>
<h3 id="_2024-01-24" tabindex="-1">2024-01-24 <a class="header-anchor" href="#_2024-01-24" aria-label="Permalink to &quot;2024-01-24&quot;"></a></h3>
<ul>
<li>pull request from <a href="https://github.com/giocic2/" target="_blank" rel="noreferrer">giocic2</a>: fixed unwanted bfseries redefinition</li>
</ul>
<h3 id="_2024-01-04" tabindex="-1">2024-01-04 <a class="header-anchor" href="#_2024-01-04" aria-label="Permalink to &quot;2024-01-04&quot;"></a></h3>
<ul>
<li>fixed wrong <code>\cvlinkedin</code> command</li>
</ul>
<h3 id="_2023-12-31" tabindex="-1">2023-12-31 <a class="header-anchor" href="#_2023-12-31" aria-label="Permalink to &quot;2023-12-31&quot;"></a></h3>
<p>Added options to transform address and skype rows into urls</p>
<h3 id="_2023-09-01" tabindex="-1">2023-09-01 <a class="header-anchor" href="#_2023-09-01" aria-label="Permalink to &quot;2023-09-01&quot;"></a></h3>
<ul>
<li>now it's possible to add an arbitrary number of skill bar sections with custom section titles, custom legends and custom skill values (see <code>\customskills</code> command)</li>
<li>improved clarity of <code>\skills</code> command</li>
<li>added some explanations about header and info profile sections</li>
<li>renamed <code>\cvdate</code> to <code>\cvbirthdate</code>, changed its icon to faCalendar</li>
<li>accepted a <a href="https://github.com/trincadev/cv-latex-twentyoneseconds/pull/1" target="_blank" rel="noreferrer">pull request</a> from <a href="https://github.com/JeromeHen/" target="_blank" rel="noreferrer">JeromeHen</a></li>
</ul>
<h3 id="_2023-05-15" tabindex="-1">2023-05-15 <a class="header-anchor" href="#_2023-05-15" aria-label="Permalink to &quot;2023-05-15&quot;"></a></h3>
<p>I added some options about:</p>
<ul>
<li>a way to hide the picture and display only the name / surname and job description row</li>
<li>custom side sections</li>
<li>removed a broken command (<code>\skillstext</code>)</li>
</ul>
<p><a href="https://www.latex-project.org/" target="_blank" rel="noreferrer"><img src="https://img.shields.io/badge/Made%20with-LaTeX-1f425f.svg" alt="made-with-latex"></a></p>
<p><a href="https://www.latex-project.org/lppl/" target="_blank" rel="noreferrer"><img src="https://img.shields.io/badge/license-LPPL-green" alt="LaTeX Project Public Licensee"></a></p>
<p><a href="https://github.com/trincadev/cv-latex-twentyoneseconds" target="_blank" rel="noreferrer"><img src="https://badges.frapsoft.com/os/v3/open-source.svg?v=103" alt="GitHub repository"></a></p>
<p><a href="https://paypal.me/trinkuz?country.x=IT&amp;locale.x=en_US" target="_blank" rel="noreferrer"><img src="https://img.shields.io/badge/Paypal-Donate%20to%20author-blue" alt="Donate"></a></p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[TwentyOneSeconds - un template per CV scritti in LaTeX]]></title>
            <link>/it/blog/latex-cv</link>
            <guid isPermaLink="false">/it/blog/latex-cv</guid>
            <pubDate>Wed, 23 Aug 2023 18:11:32 GMT</pubDate>
            <description><![CDATA[Come scrivere un CV professionale usando questo template LaTeX a due colonne multi-pagina]]></description>
            <content:encoded><![CDATA[<h1 id="twentyoneseconds-un-template-per-cv-scritti-in-latex" tabindex="-1">TwentyOneSeconds - un template per CV scritti in LaTeX <a class="header-anchor" href="#twentyoneseconds-un-template-per-cv-scritti-in-latex" aria-label="Permalink to &quot;TwentyOneSeconds - un template per CV scritti in LaTeX&quot;"></a></h1>
<p>Ho iniziato dal tema LaTeX di <a href="https://github.com/spagnuolocarmine/TwentySecondsCurriculumVitae-LaTex" target="_blank" rel="noreferrer">twentyseconds</a> perché penso che <a href="https://www.quora.com/Is-building-a-resume-in-LaTeX-a-good-idea" target="_blank" rel="noreferrer">un CV in LaTeX sembri più professionale</a>. E' anche possibile fare cose <a href="https://xkcd.com/1676/" target="_blank" rel="noreferrer">come questa</a>:</p>
<p><img src="https://imgs.xkcd.com/comics/full_width_justification.png" alt="a-bad-justification" title="A bad justification"></p>
<p>Mi piace il layout a due colonne ma non la colonna di sinistra creata all'esterno del documento (fatto che imporrebbe di scrivere il testo nella colonna di sinistra specificando esattamente le dimensioni del frame contenente il testo, rendendo di fatto inutile l'utilizzo di LaTeX).
Per replicare questa configurazione a due colonne ho pensato di usare questa <a href="https://tex.stackexchange.com/a/310989/109031" target="_blank" rel="noreferrer">soluzione</a> basata sul pacchetto <code>tcolorbox</code>.</p>
<p>Ho iniziato creando questa configurazione LaTeX di base:</p>
<div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-kHF3F" id="tab-VLnYzHH" checked><label data-title="twentyonesecondcv.cls" for="tab-VLnYzHH">twentyonesecondcv.cls</label><input type="radio" name="group-kHF3F" id="tab-ZWoDVL-" ><label data-title="cv.tex" for="tab-ZWoDVL-">cv.tex</label></div><div class="blocks">
<div class="language-latex vp-adaptive-theme active"><button title="Copy Code" class="copy"></button><span class="lang">latex</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\ProvidesClass</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{twentyonesecondcv}[2017/01/08 CV class]</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\documentclass</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">article</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\usepackage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">blindtext</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\usepackage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">xcolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\usepackage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">geometry</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\usepackage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[most]{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">tcolorbox</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\geometry</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{margin=0pt}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\definecolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{gray}{HTML}{4D4D4D}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\definecolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{sidecolor}{HTML}{E7E7E7}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\definecolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{mainblue}{HTML}{0E5484}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\definecolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{maingray}{HTML}{B9B9B9}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\newtcolorbox</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}[1][]{nobeforeafter,leftright skip=0pt,boxrule=0pt,enhanced jigsaw,sharp corners,#1}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\newcommand</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\sidesection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}[1]{</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \noindent</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \begin</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}[height=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\paperheight</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,colback=sidecolor,width=0.33</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\textwidth</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    #1</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \end</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">%</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\newcommand</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\mainsection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}[1]{</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">%</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \noindent</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \begin</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}[height=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\paperheight</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,colback=white,width=0.67</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\textwidth</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    #1</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \end</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">%</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
</div><div class="language-latex vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">latex</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\documentclass</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[letterpaper]{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">twentyonesecondcv</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">% a4paper for A4</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\begin</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{document}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\noindent</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\sidesection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{col 1, pag 1:: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\lipsum</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[1-3]}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\mainsection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{col 2, pag 1:: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\textit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-dark-font-style:italic;--shiki-light:#005CC5;--shiki-light-font-style:italic">\lipsum</span><span style="--shiki-dark:#E1E4E8;--shiki-dark-font-style:italic;--shiki-light:#24292E;--shiki-light-font-style:italic">[1-5]</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\newpage</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\noindent</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\sidesection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{col 1, pag2:: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\lipsum</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[3-5]}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\mainsection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{col 2, pag2:: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\textit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-dark-font-style:italic;--shiki-light:#005CC5;--shiki-light-font-style:italic">\lipsum</span><span style="--shiki-dark:#E1E4E8;--shiki-dark-font-style:italic;--shiki-light:#24292E;--shiki-light-font-style:italic">[3-7]</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\end</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{document}</span></span></code></pre>
</div></div></div>
<p>Si può notare che l'esempio è già su due pagine: va bene mantenere il CV semplice e facile da leggere, ma cosa fare con una lista di esperienze lavorative da mostrare più lunga di una pagina?</p>
<p>Latex è vantaggioso da usare perché permette di pensare al solo contenuto testuale, senza sprecare tempo con l'impaginazione.
Il mio più grande problema era riempire l'eventuale spazio vuoto fra l'ultimo paragrafo ed il fondo della colonna: comandi come <code>\vfill</code> non funzionano all'interno di <code>newtcolorbox</code> perché il frame non ha un'altezza fissa.</p>
<p>Fortunatamente (dovrebbe essere dalla versione 3.70 di <code>newtcolorbox</code>) è possibile modificare questo comportamento; per questo serviranno le opzioni</p>
<ul>
<li><code>height=&lt;value&gt;</code></li>
<li><code>text fill</code></li>
</ul>
<p>Come già detto, il frame non può usare <code>\vfill</code> perché inizialmente non ha un'altezza fissa. Per imporre l'altezza del contenitore del testo uguale all'altezza dell'intera pagina serve <code>height=\heigthpage</code>.</p>
<p>L'ultima precauzione è evitare comandi che impongano valori fissi come quello <code>tikzpicture</code> usato all'interno del comando personalizzato <code>\makeheaderprofile</code>.</p>
<p>Per qualche ragione invece i comandi personalizzati usati per creare le sezioni delle abilità (<code>\skills</code> e <code>\skillstext</code>) funzionano bene.</p>
<p>Finalmente <a href="/trinca_public.pdf">qui</a> il risultato ed il <a href="https://github.com/trincadev/cv-latex-twentyoneseconds" target="_blank" rel="noreferrer">repository GitHub</a>!</p>
<p>Ho pubblicato il template LaTeX di esempio <a href="https://www.overleaf.com/latex/templates/twentyoneseconds/xmvbqtfmnycf" target="_blank" rel="noreferrer">qui su Overleaf</a>.</p>
<h2 id="updates" tabindex="-1">Updates <a class="header-anchor" href="#updates" aria-label="Permalink to &quot;Updates&quot;"></a></h2>
<h3 id="_2024-06-26" tabindex="-1">2024-06-26 <a class="header-anchor" href="#_2024-06-26" aria-label="Permalink to &quot;2024-06-26&quot;"></a></h3>
<ul>
<li>Aggiunto file con la LICENSE</li>
<li>aggiunti comandi alternativi per creare il testo nel side footer</li>
</ul>
<h3 id="_2024-01-04" tabindex="-1">2024-01-04 <a class="header-anchor" href="#_2024-01-04" aria-label="Permalink to &quot;2024-01-04&quot;"></a></h3>
<ul>
<li>
<p>fixed wrong <code>\cvlinkedin</code> command</p>
</li>
<li>
<p><a href="https://github.com/trincadev/cv-latex-twentyoneseconds/pull/5" target="_blank" rel="noreferrer">pull request</a> da <a href="https://github.com/giocic2/" target="_blank" rel="noreferrer">giocic2</a>: corretta la definizione di <code>bfseries</code></p>
</li>
</ul>
<h3 id="_04-01-2023" tabindex="-1">04-01-2023 <a class="header-anchor" href="#_04-01-2023" aria-label="Permalink to &quot;04-01-2023&quot;"></a></h3>
<ul>
<li>corretto il comando <code>\cvlinkedin</code></li>
</ul>
<h3 id="_31-12-2023" tabindex="-1">31-12-2023 <a class="header-anchor" href="#_31-12-2023" aria-label="Permalink to &quot;31-12-2023&quot;"></a></h3>
<p>Aggiunte opzioni per trasformare indirizzi Skype in URL</p>
<h3 id="_01-09-2023" tabindex="-1">01-09-2023 <a class="header-anchor" href="#_01-09-2023" aria-label="Permalink to &quot;01-09-2023&quot;"></a></h3>
<ul>
<li>ora è possibile aggiungere un numero arbitrario di sezioni della barra delle <em>skill</em> con titoli di sezione personalizzabili, legende e <em>skill</em> personalizzabili (vedi comando <code>\customskills</code>)</li>
<li>migliorata la chiarezza del comando <code>\skills</code></li>
<li>aggiunte alcune spiegazioni sulle sezioni dell'intestazione e del profilo informativo</li>
<li>rinominato <code>\cvdate</code> in <code>\cvbirthdate</code>, cambiata la sua icona in faCalendar</li>
<li>accettato una <a href="https://github.com/trincadev/cv-latex-twentyoneseconds/pull/1" target="_blank" rel="noreferrer">pull request</a> da <a href="https://github.com/JeromeHen/" target="_blank" rel="noreferrer">JeromeHen</a></li>
</ul>
<h3 id="_2023-05-15" tabindex="-1">2023-05-15 <a class="header-anchor" href="#_2023-05-15" aria-label="Permalink to &quot;2023-05-15&quot;"></a></h3>
<p>Aggiunte alcune opzioni:</p>
<ul>
<li>un modo per nascondere l'immagine e visualizzare solo la riga con nome/cognome ed descrizione della mansione lavorativa</li>
<li>sezioni laterali personalizzabili</li>
<li>rimosso un comando non funzionante (<code>\skillstext</code>)</li>
</ul>
<p><a href="https://www.latex-project.org/" target="_blank" rel="noreferrer"><img src="https://img.shields.io/badge/Made%20with-LaTeX-1f425f.svg" alt="realizzato-con-latex"></a></p>
<p><a href="https://www.latex-project.org/lppl/" target="_blank" rel="noreferrer"><img src="https://img.shields.io/badge/license-LPPL-green" alt="LaTeX Project Public Licensee"></a></p>
<p><a href="https://github.com/trincadev/cv-latex-twentyoneseconds" target="_blank" rel="noreferrer"><img src="https://badges.frapsoft.com/os/v3/open-source.svg?v=103" alt="repository GitHub"></a></p>
<p><a href="https://paypal.me/trinkuz?country.x=IT&amp;locale.x=it_IT" target="_blank" rel="noreferrer"><img src="https://img.shields.io/badge/Paypal-Donate%20to%20author-blue" alt="Dona"></a></p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[TwentyOneSeconds - un template per CV scritti in LaTeX]]></title>
            <link>/it/projects/latex-cv</link>
            <guid isPermaLink="false">/it/projects/latex-cv</guid>
            <pubDate>Wed, 23 Aug 2023 18:11:32 GMT</pubDate>
            <description><![CDATA[Come scrivere un CV professionale usando questo template LaTeX a due colonne multi-pagina]]></description>
            <content:encoded><![CDATA[<h1 id="twentyoneseconds-un-template-per-cv-scritti-in-latex" tabindex="-1">TwentyOneSeconds - un template per CV scritti in LaTeX <a class="header-anchor" href="#twentyoneseconds-un-template-per-cv-scritti-in-latex" aria-label="Permalink to &quot;TwentyOneSeconds - un template per CV scritti in LaTeX&quot;"></a></h1>
<p>Ho iniziato dal tema LaTeX di <a href="https://github.com/spagnuolocarmine/TwentySecondsCurriculumVitae-LaTex" target="_blank" rel="noreferrer">twentyseconds</a> perché penso che <a href="https://www.quora.com/Is-building-a-resume-in-LaTeX-a-good-idea" target="_blank" rel="noreferrer">un CV in LaTeX sembri più professionale</a>. E' anche possibile fare cose <a href="https://xkcd.com/1676/" target="_blank" rel="noreferrer">come questa</a>:</p>
<p><img src="https://imgs.xkcd.com/comics/full_width_justification.png" alt="a-bad-justification" title="A bad justification"></p>
<p>Mi piace il layout a due colonne ma non la colonna di sinistra creata all'esterno del documento (fatto che imporrebbe di scrivere il testo nella colonna di sinistra specificando esattamente le dimensioni del frame contenente il testo, rendendo di fatto inutile l'utilizzo di LaTeX).
Per replicare questa configurazione a due colonne ho pensato di usare questa <a href="https://tex.stackexchange.com/a/310989/109031" target="_blank" rel="noreferrer">soluzione</a> basata sul pacchetto <code>tcolorbox</code>.</p>
<p>Ho iniziato creando questa configurazione LaTeX di base:</p>
<div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-bj7in" id="tab-nnTAEuK" checked><label data-title="twentyonesecondcv.cls" for="tab-nnTAEuK">twentyonesecondcv.cls</label><input type="radio" name="group-bj7in" id="tab-P-AYQnU" ><label data-title="cv.tex" for="tab-P-AYQnU">cv.tex</label></div><div class="blocks">
<div class="language-latex vp-adaptive-theme active"><button title="Copy Code" class="copy"></button><span class="lang">latex</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\ProvidesClass</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{twentyonesecondcv}[2017/01/08 CV class]</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\documentclass</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">article</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\usepackage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">blindtext</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\usepackage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">xcolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\usepackage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">geometry</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\usepackage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[most]{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">tcolorbox</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\geometry</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{margin=0pt}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\definecolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{gray}{HTML}{4D4D4D}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\definecolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{sidecolor}{HTML}{E7E7E7}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\definecolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{mainblue}{HTML}{0E5484}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\definecolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{maingray}{HTML}{B9B9B9}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\newtcolorbox</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}[1][]{nobeforeafter,leftright skip=0pt,boxrule=0pt,enhanced jigsaw,sharp corners,#1}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\newcommand</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\sidesection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}[1]{</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \noindent</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \begin</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}[height=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\paperheight</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,colback=sidecolor,width=0.33</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\textwidth</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    #1</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \end</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">%</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\newcommand</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\mainsection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}[1]{</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">%</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \noindent</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \begin</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}[height=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\paperheight</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,colback=white,width=0.67</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\textwidth</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    #1</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \end</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">%</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
</div><div class="language-latex vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">latex</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\documentclass</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[letterpaper]{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">twentyonesecondcv</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">% a4paper for A4</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\begin</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{document}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\noindent</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\sidesection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{col 1, pag 1:: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\lipsum</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[1-3]}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\mainsection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{col 2, pag 1:: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\textit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-dark-font-style:italic;--shiki-light:#005CC5;--shiki-light-font-style:italic">\lipsum</span><span style="--shiki-dark:#E1E4E8;--shiki-dark-font-style:italic;--shiki-light:#24292E;--shiki-light-font-style:italic">[1-5]</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\newpage</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\noindent</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\sidesection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{col 1, pag2:: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\lipsum</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[3-5]}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\mainsection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{col 2, pag2:: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\textit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-dark-font-style:italic;--shiki-light:#005CC5;--shiki-light-font-style:italic">\lipsum</span><span style="--shiki-dark:#E1E4E8;--shiki-dark-font-style:italic;--shiki-light:#24292E;--shiki-light-font-style:italic">[3-7]</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\end</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{document}</span></span></code></pre>
</div></div></div>
<p>Si può notare che l'esempio è già su due pagine: va bene mantenere il CV semplice e facile da leggere, ma cosa fare con una lista di esperienze lavorative da mostrare più lunga di una pagina?</p>
<p>Latex è vantaggioso da usare perché permette di pensare al solo contenuto testuale, senza sprecare tempo con l'impaginazione.
Il mio più grande problema era riempire l'eventuale spazio vuoto fra l'ultimo paragrafo ed il fondo della colonna: comandi come <code>\vfill</code> non funzionano all'interno di <code>newtcolorbox</code> perché il frame non ha un'altezza fissa.</p>
<p>Fortunatamente (dovrebbe essere dalla versione 3.70 di <code>newtcolorbox</code>) è possibile modificare questo comportamento; per questo serviranno le opzioni</p>
<ul>
<li><code>height=&lt;value&gt;</code></li>
<li><code>text fill</code></li>
</ul>
<p>Come già detto, il frame non può usare <code>\vfill</code> perché inizialmente non ha un'altezza fissa. Per imporre l'altezza del contenitore del testo uguale all'altezza dell'intera pagina serve <code>height=\heigthpage</code>.</p>
<p>L'ultima precauzione è evitare comandi che impongano valori fissi come quello <code>tikzpicture</code> usato all'interno del comando personalizzato <code>\makeheaderprofile</code>.</p>
<p>Per qualche ragione invece i comandi personalizzati usati per creare le sezioni delle abilità (<code>\skills</code> e <code>\skillstext</code>) funzionano bene.</p>
<p>Finalmente <a href="/trinca_public.pdf">qui</a> il risultato ed il <a href="https://github.com/trincadev/cv-latex-twentyoneseconds" target="_blank" rel="noreferrer">repository GitHub</a>!</p>
<p>Ho pubblicato il template LaTeX di esempio <a href="https://www.overleaf.com/latex/templates/twentyoneseconds/xmvbqtfmnycf" target="_blank" rel="noreferrer">qui su Overleaf</a>.</p>
<h2 id="updates" tabindex="-1">Updates <a class="header-anchor" href="#updates" aria-label="Permalink to &quot;Updates&quot;"></a></h2>
<h3 id="_2024-06-26" tabindex="-1">2024-06-26 <a class="header-anchor" href="#_2024-06-26" aria-label="Permalink to &quot;2024-06-26&quot;"></a></h3>
<ul>
<li>Aggiunto file con la LICENSE</li>
<li>aggiunti comandi alternativi per creare il testo nel side footer</li>
</ul>
<h3 id="_2024-01-04" tabindex="-1">2024-01-04 <a class="header-anchor" href="#_2024-01-04" aria-label="Permalink to &quot;2024-01-04&quot;"></a></h3>
<ul>
<li>
<p>fixed wrong <code>\cvlinkedin</code> command</p>
</li>
<li>
<p><a href="https://github.com/trincadev/cv-latex-twentyoneseconds/pull/5" target="_blank" rel="noreferrer">pull request</a> da <a href="https://github.com/giocic2/" target="_blank" rel="noreferrer">giocic2</a>: corretta la definizione di <code>bfseries</code></p>
</li>
</ul>
<h3 id="_04-01-2023" tabindex="-1">04-01-2023 <a class="header-anchor" href="#_04-01-2023" aria-label="Permalink to &quot;04-01-2023&quot;"></a></h3>
<ul>
<li>corretto il comando <code>\cvlinkedin</code></li>
</ul>
<h3 id="_31-12-2023" tabindex="-1">31-12-2023 <a class="header-anchor" href="#_31-12-2023" aria-label="Permalink to &quot;31-12-2023&quot;"></a></h3>
<p>Aggiunte opzioni per trasformare indirizzi Skype in URL</p>
<h3 id="_01-09-2023" tabindex="-1">01-09-2023 <a class="header-anchor" href="#_01-09-2023" aria-label="Permalink to &quot;01-09-2023&quot;"></a></h3>
<ul>
<li>ora è possibile aggiungere un numero arbitrario di sezioni della barra delle <em>skill</em> con titoli di sezione personalizzabili, legende e <em>skill</em> personalizzabili (vedi comando <code>\customskills</code>)</li>
<li>migliorata la chiarezza del comando <code>\skills</code></li>
<li>aggiunte alcune spiegazioni sulle sezioni dell'intestazione e del profilo informativo</li>
<li>rinominato <code>\cvdate</code> in <code>\cvbirthdate</code>, cambiata la sua icona in faCalendar</li>
<li>accettato una <a href="https://github.com/trincadev/cv-latex-twentyoneseconds/pull/1" target="_blank" rel="noreferrer">pull request</a> da <a href="https://github.com/JeromeHen/" target="_blank" rel="noreferrer">JeromeHen</a></li>
</ul>
<h3 id="_2023-05-15" tabindex="-1">2023-05-15 <a class="header-anchor" href="#_2023-05-15" aria-label="Permalink to &quot;2023-05-15&quot;"></a></h3>
<p>Aggiunte alcune opzioni:</p>
<ul>
<li>un modo per nascondere l'immagine e visualizzare solo la riga con nome/cognome ed descrizione della mansione lavorativa</li>
<li>sezioni laterali personalizzabili</li>
<li>rimosso un comando non funzionante (<code>\skillstext</code>)</li>
</ul>
<p><a href="https://www.latex-project.org/" target="_blank" rel="noreferrer"><img src="https://img.shields.io/badge/Made%20with-LaTeX-1f425f.svg" alt="realizzato-con-latex"></a></p>
<p><a href="https://www.latex-project.org/lppl/" target="_blank" rel="noreferrer"><img src="https://img.shields.io/badge/license-LPPL-green" alt="LaTeX Project Public Licensee"></a></p>
<p><a href="https://github.com/trincadev/cv-latex-twentyoneseconds" target="_blank" rel="noreferrer"><img src="https://badges.frapsoft.com/os/v3/open-source.svg?v=103" alt="repository GitHub"></a></p>
<p><a href="https://paypal.me/trinkuz?country.x=IT&amp;locale.x=it_IT" target="_blank" rel="noreferrer"><img src="https://img.shields.io/badge/Paypal-Donate%20to%20author-blue" alt="Dona"></a></p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[TwentyOneSeconds - a CV LaTeX template]]></title>
            <link>/projects/latex-cv</link>
            <guid isPermaLink="false">/projects/latex-cv</guid>
            <pubDate>Wed, 23 Aug 2023 18:11:32 GMT</pubDate>
            <description><![CDATA[Write a professional CV using this multi-page two-column LaTeX template]]></description>
            <content:encoded><![CDATA[<h1 id="twentyoneseconds-a-cv-latex-template" tabindex="-1">TwentyOneSeconds - a CV LaTeX template <a class="header-anchor" href="#twentyoneseconds-a-cv-latex-template" aria-label="Permalink to &quot;TwentyOneSeconds - a CV LaTeX template&quot;"></a></h1>
<p>I started from the <a href="https://github.com/spagnuolocarmine/TwentySecondsCurriculumVitae-LaTex" target="_blank" rel="noreferrer">twentyseconds</a> LaTeX theme
because I think <a href="https://www.quora.com/Is-building-a-resume-in-LaTeX-a-good-idea" target="_blank" rel="noreferrer">a LaTeX CV looks more professional</a>. It's also possible to do things <a href="https://xkcd.com/1676/" target="_blank" rel="noreferrer">like this</a>:</p>
<p><img src="https://imgs.xkcd.com/comics/full_width_justification.png" alt="a-bad-justification" title="A bad justification"></p>
<p>I like the two-column layout but not the left column created outside the document.
Because of this I should set the dimensions of the second column exactly like those of the frame containing the text, making the use of LaTeX useless.</p>
<p>To replicate this two-column configuration I thought to use this clever <a href="https://tex.stackexchange.com/a/310989/109031" target="_blank" rel="noreferrer">solution</a> based on the <code>tcolorbox</code> package.</p>
<p>First of all I created this basic LaTeX document:</p>
<div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-Vfe-l" id="tab-ZQdgfCs" checked><label data-title="twentyonesecondcv.cls" for="tab-ZQdgfCs">twentyonesecondcv.cls</label><input type="radio" name="group-Vfe-l" id="tab-LGUeuPo" ><label data-title="cv.tex" for="tab-LGUeuPo">cv.tex</label></div><div class="blocks">
<div class="language-latex vp-adaptive-theme active"><button title="Copy Code" class="copy"></button><span class="lang">latex</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\ProvidesClass</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{twentyonesecondcv}[2017/01/08 CV class]</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\documentclass</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">article</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\usepackage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">blindtext</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\usepackage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">xcolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\usepackage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">geometry</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\usepackage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[most]{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">tcolorbox</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\geometry</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{margin=0pt}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\definecolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{gray}{HTML}{4D4D4D}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\definecolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{sidecolor}{HTML}{E7E7E7}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\definecolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{mainblue}{HTML}{0E5484}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\definecolor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{maingray}{HTML}{B9B9B9}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\newtcolorbox</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}[1][]{nobeforeafter,leftright skip=0pt,boxrule=0pt,enhanced jigsaw,sharp corners,#1}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\newcommand</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\sidesection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}[1]{</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \noindent</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \begin</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}[height=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\paperheight</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,colback=sidecolor,width=0.33</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\textwidth</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    #1</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \end</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">%</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\newcommand</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\mainsection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}[1]{</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">%</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \noindent</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \begin</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}[height=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\paperheight</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,colback=white,width=0.67</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\textwidth</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    #1</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    \end</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{bgbox}</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">%</span></span>
<span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre>
</div><div class="language-latex vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">latex</span><pre class="shiki shiki-themes github-dark github-light vp-code" tabindex="0" v-pre=""><code><span class="line"></span>
<span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">\documentclass</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[letterpaper]{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">twentyonesecondcv</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">% a4paper for A4</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\begin</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{document}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\noindent</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\sidesection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{col 1, pag 1:: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\lipsum</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[1-3]}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\mainsection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{col 2, pag 1:: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\textit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-dark-font-style:italic;--shiki-light:#005CC5;--shiki-light-font-style:italic">\lipsum</span><span style="--shiki-dark:#E1E4E8;--shiki-dark-font-style:italic;--shiki-light:#24292E;--shiki-light-font-style:italic">[1-5]</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\newpage</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\noindent</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\sidesection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{col 1, pag2:: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\lipsum</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[3-5]}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\mainsection</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{col 2, pag2:: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\textit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-dark-font-style:italic;--shiki-light:#005CC5;--shiki-light-font-style:italic">\lipsum</span><span style="--shiki-dark:#E1E4E8;--shiki-dark-font-style:italic;--shiki-light:#24292E;--shiki-light-font-style:italic">[3-7]</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}}</span></span>
<span class="line"><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\end</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{document}</span></span></code></pre>
</div></div></div>
<p>You can notice that this example is already on two pages: it's ok to keep your CV simple and quick to read, but what if you have a long list of experiences to show?</p>
<p>Of course LaTeX is good because the markup engine should keep you free to think about your content, not concentrating on handling manual placements of borders, paragraph positions and so on. My biggest problem about this was filling empty space: commands like <code>\vfill</code> aren't working within <code>newtcolorbox</code> because the box has no fixed height.</p>
<p>Luckily (should be from version 3.70) it's possible to change this behavior; for this you need the options</p>
<ul>
<li><code>height=&lt;value&gt;</code></li>
<li><code>text fill</code></li>
</ul>
<p>As already said, the box can't use <code>\vfill</code> because initially it hasn't a fixed height, so these options are changing that. I use <code>height=\heigthpage</code> because my boxes' height are equal to the entire page height.</p>
<p>Last precaution is to avoid commands enforcing fixed value settings like the <code>tikzpicture</code> one used within the custom command <code>\makeheaderprofile</code>.</p>
<p>For some reason instead the custom commands used to create the skill sections (\skills and \skillstext) work fine.</p>
<p>Finally <a href="/trinca_public.pdf">here</a> is the result and the <a href="https://github.com/trincadev/cv-latex-twentyoneseconds" target="_blank" rel="noreferrer">GitHub repository</a>!</p>
<p>I published this LaTeX template on this <a href="https://www.overleaf.com/latex/templates/twentyoneseconds/xmvbqtfmnycf" target="_blank" rel="noreferrer">Overleaf page</a>.</p>
<h2 id="updates" tabindex="-1">Updates <a class="header-anchor" href="#updates" aria-label="Permalink to &quot;Updates&quot;"></a></h2>
<h3 id="_2024-06-26" tabindex="-1">2024-06-26 <a class="header-anchor" href="#_2024-06-26" aria-label="Permalink to &quot;2024-06-26&quot;"></a></h3>
<ul>
<li>Add LICENSE file</li>
<li>add alternative commands to create the side footer text</li>
</ul>
<h3 id="_2024-01-24" tabindex="-1">2024-01-24 <a class="header-anchor" href="#_2024-01-24" aria-label="Permalink to &quot;2024-01-24&quot;"></a></h3>
<ul>
<li>pull request from <a href="https://github.com/giocic2/" target="_blank" rel="noreferrer">giocic2</a>: fixed unwanted bfseries redefinition</li>
</ul>
<h3 id="_2024-01-04" tabindex="-1">2024-01-04 <a class="header-anchor" href="#_2024-01-04" aria-label="Permalink to &quot;2024-01-04&quot;"></a></h3>
<ul>
<li>fixed wrong <code>\cvlinkedin</code> command</li>
</ul>
<h3 id="_2023-12-31" tabindex="-1">2023-12-31 <a class="header-anchor" href="#_2023-12-31" aria-label="Permalink to &quot;2023-12-31&quot;"></a></h3>
<p>Added options to transform address and skype rows into urls</p>
<h3 id="_2023-09-01" tabindex="-1">2023-09-01 <a class="header-anchor" href="#_2023-09-01" aria-label="Permalink to &quot;2023-09-01&quot;"></a></h3>
<ul>
<li>now it's possible to add an arbitrary number of skill bar sections with custom section titles, custom legends and custom skill values (see <code>\customskills</code> command)</li>
<li>improved clarity of <code>\skills</code> command</li>
<li>added some explanations about header and info profile sections</li>
<li>renamed <code>\cvdate</code> to <code>\cvbirthdate</code>, changed its icon to faCalendar</li>
<li>accepted a <a href="https://github.com/trincadev/cv-latex-twentyoneseconds/pull/1" target="_blank" rel="noreferrer">pull request</a> from <a href="https://github.com/JeromeHen/" target="_blank" rel="noreferrer">JeromeHen</a></li>
</ul>
<h3 id="_2023-05-15" tabindex="-1">2023-05-15 <a class="header-anchor" href="#_2023-05-15" aria-label="Permalink to &quot;2023-05-15&quot;"></a></h3>
<p>I added some options about:</p>
<ul>
<li>a way to hide the picture and display only the name / surname and job description row</li>
<li>custom side sections</li>
<li>removed a broken command (<code>\skillstext</code>)</li>
</ul>
<p><a href="https://www.latex-project.org/" target="_blank" rel="noreferrer"><img src="https://img.shields.io/badge/Made%20with-LaTeX-1f425f.svg" alt="made-with-latex"></a></p>
<p><a href="https://www.latex-project.org/lppl/" target="_blank" rel="noreferrer"><img src="https://img.shields.io/badge/license-LPPL-green" alt="LaTeX Project Public Licensee"></a></p>
<p><a href="https://github.com/trincadev/cv-latex-twentyoneseconds" target="_blank" rel="noreferrer"><img src="https://badges.frapsoft.com/os/v3/open-source.svg?v=103" alt="GitHub repository"></a></p>
<p><a href="https://paypal.me/trinkuz?country.x=IT&amp;locale.x=en_US" target="_blank" rel="noreferrer"><img src="https://img.shields.io/badge/Paypal-Donate%20to%20author-blue" alt="Donate"></a></p>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[Una camminata in Val Masino]]></title>
            <link>/it/photo-galleries/val-masino-20221023</link>
            <guid isPermaLink="false">/it/photo-galleries/val-masino-20221023</guid>
            <pubDate>Sun, 23 Oct 2022 12:11:00 GMT</pubDate>
            <description><![CDATA[Ritornando negli scenari mozzafiato della Riserva Naturale della Val Masino]]></description>
            <content:encoded><![CDATA[<h1 id="val-masino-23-ottobre-2022" tabindex="-1">Val Masino, 23 Ottobre 2022 <a class="header-anchor" href="#val-masino-23-ottobre-2022" aria-label="Permalink to &quot;Val Masino, 23 Ottobre 2022&quot;"></a></h1>
<h2 id="una-camminata-in-val-masino" tabindex="-1">Una camminata in Val Masino <a class="header-anchor" href="#una-camminata-in-val-masino" aria-label="Permalink to &quot;Una camminata in Val Masino&quot;"></a></h2>
<div id="map-container">
    <MapWithPolyline
      :center='[46.2263, 9.63513]'
      location="Val Masino"
      :mapName="mapName"
      :pathUrl="`/paths/${mapName}.json`"
      :photosUrl="`/photos/${mapName}.json`"
      startPoint="Albergo Rustichella, 23010, Val Masino (SO)"
      zoom=15
    />
</div>
<h2 id="trentasei-vedute-della-val-masino" tabindex="-1">Trentasei vedute della Val Masino <a class="header-anchor" href="#trentasei-vedute-della-val-masino" aria-label="Permalink to &quot;Trentasei vedute della Val Masino&quot;"></a></h2>
<p>Foto scattate il 23 Ottobre 2022.</p>
<div id="image-gallery-container">
    <SimpleGallery
      :galleryID="mapName"
      :imagesData="imagesData"
    />
</div>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
        <item>
            <title><![CDATA[A walk in Val Masino]]></title>
            <link>/photo-galleries/val-masino-20221023</link>
            <guid isPermaLink="false">/photo-galleries/val-masino-20221023</guid>
            <pubDate>Sun, 23 Oct 2022 12:11:00 GMT</pubDate>
            <description><![CDATA[Returning to Val Masino breathtaking scenery]]></description>
            <content:encoded><![CDATA[<h1 id="val-masino-23-october-2022" tabindex="-1">Val Masino, 23 October 2022 <a class="header-anchor" href="#val-masino-23-october-2022" aria-label="Permalink to &quot;Val Masino, 23 October 2022&quot;"></a></h1>
<h2 id="a-walk-in-val-masino" tabindex="-1">A Walk in Val Masino <a class="header-anchor" href="#a-walk-in-val-masino" aria-label="Permalink to &quot;A Walk in Val Masino&quot;"></a></h2>
<div id="map-container">
    <MapWithPolyline
      :center='[46.2263, 9.63513]'
      location="Val Masino"
      :mapName="mapName"
      :pathUrl="`/paths/${mapName}.json`"
      :photosUrl="`/photos/${mapName}.json`"
      startPoint="Albergo Rustichella, 23010, Val Masino (SO)"
      zoom=15
    />
</div>
<h2 id="thirty-six-views-of-val-masino" tabindex="-1">Thirty-six views of Val Masino <a class="header-anchor" href="#thirty-six-views-of-val-masino" aria-label="Permalink to &quot;Thirty-six views of Val Masino&quot;"></a></h2>
<p>Photos taken on 23 October 2022.</p>
<div id="image-gallery-container">
    <SimpleGallery
      :galleryID="mapName"
      :imagesData="imagesData"
    />
</div>
]]></content:encoded>
            <author>alessandro@trinca.tornidor.com (Alessandro Trinca Torndidor)</author>
        </item>
    </channel>
</rss>