<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: claudio matko</title>
    <description>The latest articles on DEV Community by claudio matko (@matko).</description>
    <link>https://dev.to/matko</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F652170%2F70b6919d-eaeb-4643-a81e-d18c64b37883.jpg</url>
      <title>DEV Community: claudio matko</title>
      <link>https://dev.to/matko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/matko"/>
    <language>en</language>
    <item>
      <title>Automatización para la limpieza masiva de gaps basada en Python, QGIS headless y NextGIS Toolbox SDK</title>
      <dc:creator>claudio matko</dc:creator>
      <pubDate>Mon, 24 Aug 2026 19:51:47 +0000</pubDate>
      <link>https://dev.to/matko/limpiar-gaps-de-miles-de-poligonos-automaticamente-con-python-qgis-headless-y-nextgis-toolbox-sdk-11im</link>
      <guid>https://dev.to/matko/limpiar-gaps-de-miles-de-poligonos-automaticamente-con-python-qgis-headless-y-nextgis-toolbox-sdk-11im</guid>
      <description>&lt;p&gt;Imaginá que tenés más de &lt;strong&gt;12000 registros&lt;/strong&gt; en una base de datos espacial, cada uno con un polígono que representa una parcela, una zona urbana o un hábitat. Al primer análisis visual, descubrís que:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Algunos polígonos tienen &lt;strong&gt;auto-intersecciones&lt;/strong&gt; (mal dibujados).&lt;/li&gt;
&lt;li&gt;Los bordes de polígonos adyacentes &lt;strong&gt;no coinciden&lt;/strong&gt; (cada uno tiene sus propios nodos).&lt;/li&gt;
&lt;li&gt;Entre muchos de ellos hay &lt;strong&gt;huecos (gaps)&lt;/strong&gt; que deberían estar cerrados.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La corrección manual mediante herramientas convencionales de digitalización es inviable en conjuntos de datos de gran escala, por lo que se propone un &lt;strong&gt;&lt;em&gt;flujo automatizado&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;En este artículo te muestro cómo construir un &lt;strong&gt;pipeline en Python&lt;/strong&gt; que combina:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NextGIS Toolbox&lt;/strong&gt; (nube, vía SDK) para reparar geometrías inválidas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QGIS Process&lt;/strong&gt; (local, sin interfaz gráfica) para cerrar gaps y limpiar residuos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El resultado: una capa con un &lt;strong&gt;mismo número de features&lt;/strong&gt;, pero con geometrías válidas y aristas coincidentes.&lt;/p&gt;




&lt;h2&gt;
  
  
  El problema: dos tipos de errores
&lt;/h2&gt;

&lt;p&gt;En el mundo de los datos geográficos, los polígonos pueden sufrir dos tipos de problemas muy distintos:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problema&lt;/th&gt;
&lt;th&gt;Naturaleza&lt;/th&gt;
&lt;th&gt;Ejemplo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Auto-intersección&lt;/strong&gt; (Self‑intersection)&lt;/td&gt;
&lt;td&gt;Validez OGC (intra‑polígono)&lt;/td&gt;
&lt;td&gt;Un lazo que se cruza a sí mismo.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bordes no coincidentes y Gaps&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Topología (inter‑polígonos)&lt;/td&gt;
&lt;td&gt;Dos polígonos vecinos que deberían tocarse pero dejan un hueco.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;La solución no es única: &lt;strong&gt;reparar la validez no cierra gaps&lt;/strong&gt;, y &lt;strong&gt;cerrar gaps sin reparar inválidas&lt;/strong&gt; genera resultados impredecibles. Para resolver este problema es necesario contar con un &lt;em&gt;flujo de trabajo combinado&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Arquitectura del pipeline
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NextGIS Toolbox&lt;/strong&gt; en la nube (a través de su SDK Python) → nos da la validez.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QGIS&lt;/strong&gt; local (vía &lt;code&gt;qgis_process&lt;/code&gt; sin GUI) → resuelve la topología.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El flujo sería el siguiente:&lt;/p&gt;

&lt;p&gt;Capa original (+12k polígonos)&lt;br&gt;
├─ [NextGIS Toolbox] &lt;a href="https://toolbox.nextgis.com/t/check_geometries" rel="noopener noreferrer"&gt;check_geometries&lt;/a&gt; → diagnóstico de inválidas&lt;br&gt;
├─ [NextGIS Toolbox] &lt;a href="https://toolbox.nextgis.com/t/fix_geometries" rel="noopener noreferrer"&gt;fix_geometries&lt;/a&gt; → repara auto‑intersecciones&lt;br&gt;
├─ [QGIS local] &lt;a href="https://docs.qgis.org/3.22/en/docs/user_manual/processing_algs/qgis/vectorgeometry.html#snap-geometries-to-layer" rel="noopener noreferrer"&gt;snapgeometries&lt;/a&gt; → cierra gaps (mueve vértices)&lt;br&gt;
└─ [QGIS local] &lt;a href="https://docs.qgis.org/3.34/en/docs/user_manual/processing_algs/qgis/vectorgeometry.html#fix-geometries" rel="noopener noreferrer"&gt;fixgeometries&lt;/a&gt; → limpia residuos de validez&lt;/p&gt;

&lt;p&gt;Capa final (+12k polígonos válidos + aristas coincidentes)&lt;/p&gt;
&lt;h3&gt;
  
  
  Esquema del flujo de trabajo
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzxscizw91xex0az3udnl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzxscizw91xex0az3udnl.png" alt="Esquema del flujo de trabajo" width="799" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Por qué este orden?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Primero validar, porque un snap sobre geometrías inválidas puede producir resultados impredecibles o introducir nuevos errores topológicos. &lt;/li&gt;
&lt;li&gt;Después del snap, algunos vértices movidos pueden crear nuevas auto‑intersecciones; &lt;code&gt;fixgeometries&lt;/code&gt; las barre.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;¿Por qué se mantienen +12000 features?&lt;/strong&gt;&lt;br&gt;
Las herramientas &lt;code&gt;snapgeometries&lt;/code&gt; y &lt;code&gt;fixgeometries&lt;/code&gt; editan geometrías &lt;em&gt;en el lugar&lt;/em&gt;: no parten ni fusionan features (a diferencia de otras aplicaciones como &lt;code&gt;v.clean&lt;/code&gt;, que genera nuevos nodos, divide las aristas y multiplica los registros).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Nota&lt;/em&gt;&lt;/strong&gt;: &lt;em&gt;En los GIS, 'snap' (o 'snapping') es el proceso de ajustar o 'pegar' los vértices de una geometría a los vértices o aristas más cercanos de otra geometría, siempre que estén dentro de una distancia máxima (tolerancia).&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  El código: &lt;code&gt;antigaps.py&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;El script se encuentra en &lt;a href="https://github.com/matkoclaudio/antigaps" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Acá profundizo en las partes más relevantes.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Parámetros de entrada
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="n"&gt;TU_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt; &lt;span class="c1"&gt;# API key de NextGIS Toolbox (*)
&lt;/span&gt;&lt;span class="n"&gt;RUTA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;       &lt;span class="c1"&gt;# Ruta de la capa (vacío → selector gráfico)
&lt;/span&gt;&lt;span class="n"&gt;GAPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;       &lt;span class="c1"&gt;# Ancho típico de los gaps (tolerancia de snap)
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;(*) Para obtener una API key de NextGIS Toolbox, basta con obtener una cuenta de usuario en &lt;a href="https://my.nextgis.com/" rel="noopener noreferrer"&gt;my.nextgis.com&lt;/a&gt;. Luego logueado en &lt;a href="https://toolbox.nextgis.com/" rel="noopener noreferrer"&gt;toolbox.nextgis.com&lt;/a&gt; seleccionar cualquier herramienta y clic en el botón &lt;code&gt;code&lt;/code&gt;, allí:&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;toolbox = ToolboxClient('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;La API key se puede proporcionar mediante variable de entorno &lt;code&gt;TOOLBOX_API_KEY&lt;/code&gt; o archivo &lt;code&gt;~/.toolbox_key&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Selección de capa (con o sin GUI)
&lt;/h3&gt;

&lt;p&gt;El script usa &lt;code&gt;tkinter&lt;/code&gt; si está disponible (selector gráfico). Otra opción es utilizar la ruta por consola. Esto lo hace versátil para entornos de escritorio o servidores.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Paso en la nube (NextGIS Toolbox SDK)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="n"&gt;toolbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ToolboxClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Diagnóstico
&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;toolbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;check_geometries&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input_file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;toolbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CAPA&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;span class="n"&gt;toolbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CARPETA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Reparación
&lt;/span&gt;&lt;span class="n"&gt;tarea&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;toolbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fix_geometries&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input_file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;toolbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CAPA&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;span class="n"&gt;fixed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tarea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for_completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;poll_interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;toolbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CARPETA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;check_geometries&lt;/code&gt; genera dos archivos: &lt;code&gt;errors.csv&lt;/code&gt; y &lt;code&gt;errors.geojson&lt;/code&gt;.&lt;br&gt;
&lt;code&gt;fix_geometries&lt;/code&gt; devuelve &lt;code&gt;fixed.gpkg&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Paso local (QGIS headless)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;qgis_process&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;native:snapgeometries&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;INPUT=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;CAPA&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;REFERENCE_LAYER=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;CAPA&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;TOLERANCE=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TOL_SNAP&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BEHAVIOR=0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OUTPUT=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;GPKG_SNAP&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;qgis_process&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;native:fixgeometries&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;INPUT=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;GPKG_SNAP&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OUTPUT=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;FINAL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;El parámetro &lt;strong&gt;BEHAVIOR&lt;/strong&gt; define la estrategia de ajuste del algoritmo. En este script usamos &lt;code&gt;BEHAVIOR=0&lt;/code&gt;, que es el modo por defecto: mueve cada vértice al punto más cercano (nodo o segmento) dentro de la tolerancia, e inserta nuevos vértices si es necesario. Es el comportamiento más adecuado para cerrar gaps entre polígonos. Para otros casos, la herramienta ofrece hasta 7 modos distintos (ver tabla). El resultado intermedio es &lt;code&gt;snap.gpkg&lt;/code&gt;; el final, &lt;code&gt;resultado.gpkg&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Valor&lt;/th&gt;
&lt;th&gt;Descripción&lt;/th&gt;
&lt;th&gt;¿Cuándo usarlo?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mueve los vértices al punto más cercano (nodo o segmento). Si es necesario, &lt;strong&gt;inserta nuevos vértices&lt;/strong&gt; en el segmento para ajustar la geometría.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;El más común.&lt;/strong&gt; Ideal para cerrar gaps y alinear geometrías complejas.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mueve los vértices al punto más cercano, pero &lt;strong&gt;no inserta nuevos vértices&lt;/strong&gt; en los segmentos.&lt;/td&gt;
&lt;td&gt;Para minimizar cambios en la estructura de vértices.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Prioriza alinear con &lt;strong&gt;nodos existentes&lt;/strong&gt; (vértices de referencia). Si no hay nodo cerca, usa el segmento más cercano. Inserta vértices si es necesario.&lt;/td&gt;
&lt;td&gt;Para respetar los vértices originales de la referencia.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Prioriza alinear con nodos existentes, pero &lt;strong&gt;no inserta nuevos vértices&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;Para mover vértices solo a otros vértices, sin crear nuevos.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Solo mueve los vértices extremos&lt;/strong&gt; (inicio y fin de cada línea). Prioriza alinear con nodos.&lt;/td&gt;
&lt;td&gt;Ideal para líneas (no polígonos) o para conectar extremos de caminos.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Solo mueve vértices extremos, priorizando el punto más cercano (nodo o segmento).&lt;/td&gt;
&lt;td&gt;Similar al 4, pero más flexible.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Engancha solo a &lt;strong&gt;nodos ancla&lt;/strong&gt; de la misma capa.&lt;/td&gt;
&lt;td&gt;Caso muy específico para redes o topología compleja.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Engancha a nodos ancla de &lt;strong&gt;cualquier capa&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;Similar al 6, pero con varias capas de referencia.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Archivos de salida: ¿qué hace cada uno?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Archivo&lt;/th&gt;
&lt;th&gt;Origen&lt;/th&gt;
&lt;th&gt;Contenido&lt;/th&gt;
&lt;th&gt;Utilidad&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://raw.githubusercontent.com/matkoclaudio/antigaps/main/data/input/poligonos.gpkg" rel="noopener noreferrer"&gt;errors.csv&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;check_geometries&lt;/code&gt; (toolbox.nextgis)&lt;/td&gt;
&lt;td&gt;Tabla con índices (&lt;code&gt;num&lt;/code&gt;) y coordenadas de cada auto‑intersección.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Diagnóstico&lt;/strong&gt;: saber qué polígonos están mal dibujados y en qué punto exacto.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://raw.githubusercontent.com/matkoclaudio/antigaps/main/data/input/errors.geojson" rel="noopener noreferrer"&gt;errors.geojson&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;check_geometries&lt;/code&gt; (toolbox.nextgis)&lt;/td&gt;
&lt;td&gt;Los mismos errores representados como puntos georreferenciados (FeatureCollection de &lt;code&gt;Point&lt;/code&gt;).&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Visualización&lt;/strong&gt;: cargar en QGIS para localizar espacialmente las zonas problemáticas.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://raw.githubusercontent.com/matkoclaudio/antigaps/main/data/input/fixed.gpkg" rel="noopener noreferrer"&gt;fixed.gpkg&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fix_geometries&lt;/code&gt; (toolbox.nextgis)&lt;/td&gt;
&lt;td&gt;Capa completa con las geometrías inválidas reparadas (auto‑intersecciones convertidas en multipolígonos válidos).&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Base intermedia&lt;/strong&gt;: capa limpia en cuanto a validez OGC, aunque los gaps persisten.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://raw.githubusercontent.com/matkoclaudio/antigaps/main/data/input/snap.gpkg" rel="noopener noreferrer"&gt;snap.gpkg&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;snapgeometries&lt;/code&gt; (qgis_process)&lt;/td&gt;
&lt;td&gt;Capa con los vértices pegados dentro de la tolerancia; los gaps quedan cerrados.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Archivo de depuración&lt;/strong&gt;: útil para inspeccionar cómo ha actuado el snap antes de la limpieza final.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://raw.githubusercontent.com/matkoclaudio/antigaps/main/data/input/resultado.gpkg" rel="noopener noreferrer"&gt;resultado.gpkg&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fixgeometries&lt;/code&gt; (qgis_process)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Capa final&lt;/strong&gt;: geometrías válidas + aristas perfectamente coincidentes (sin gaps).&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;El entregable&lt;/strong&gt;: es el archivo que debes usar para tus análisis posteriores.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  La tolerancia: el parámetro + crítico
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;TOLERANCE&lt;/code&gt; (la variable &lt;code&gt;GAPS&lt;/code&gt; del script) es la distancia máxima que el snap considera para mover un vértice. Actúa como un umbral:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Si la tolerancia es &lt;strong&gt;menor&lt;/strong&gt; que el gap → el algoritmo no detecta los bordes cercanos y el gap persiste.&lt;/li&gt;
&lt;li&gt;Si es &lt;strong&gt;igual&lt;/strong&gt; al gap → lo cierra justo.&lt;/li&gt;
&lt;li&gt;Si es &lt;strong&gt;mayor&lt;/strong&gt; → mueve límites que no deberían moverse, distorsionando el polígono.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Consejo práctico&lt;/em&gt;&lt;/strong&gt;: &lt;em&gt;mide el ancho de los gaps reales de tu capa (con herramientas de medición en QGIS), usar el valor más grande que encuentres como tolerancia.&lt;/em&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Resultados
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0n8fe5u7jbn3at5fp8ri.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0n8fe5u7jbn3at5fp8ri.gif" alt="Total" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb3sbmzpwgtmb3kccxo4b.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb3sbmzpwgtmb3kccxo4b.gif" alt="Zoom" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Nota sobre los datos
&lt;/h3&gt;

&lt;p&gt;Los datos de entrada utilizados corresponden a un dataset sintético, generado por un script, para pruebas, no corresponde a ninguna región real. &lt;/p&gt;

&lt;p&gt;Estos datos fueron creados mediante una cuadrícula regular a la que se le aplicaron desplazamientos aleatorios en sus vértices, generando así los gaps y las auto-intersecciones que buscamos reparar.&lt;/p&gt;

&lt;p&gt;Al ser artificial, contiene errores topológicos controlados que permiten demostrar el flujo de trabajo de forma clara y reproducible. &lt;/p&gt;

&lt;p&gt;En tu propia capa de datos reales, el script debería funcionar exactamente igual, sólo no olvides de ajustar la tolerancia a tu caso (TOL_SNAP).&lt;/p&gt;

&lt;p&gt;Tanto los datos de &lt;a href="https://github.com/matkoclaudio/antigaps/tree/main/data/input" rel="noopener noreferrer"&gt;entrada&lt;/a&gt; como los &lt;a href="https://github.com/matkoclaudio/antigaps/tree/main/data/output" rel="noopener noreferrer"&gt;resultados&lt;/a&gt; se encuentran a disposición en el repositorio, para poder ensayar el uso del script sin problemas.&lt;/p&gt;

&lt;p&gt;Este pipeline puede ser útil en diversos escenarios: Catastro y ordenamiento territorial; Planificación urbana; Análisis de cobertura de suelo; Migración de datos heredados; entre otros.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mejoras futuras
&lt;/h3&gt;

&lt;p&gt;El script actual es funcional, pero como todo puede mejorar. Próximos pasos: Medición automática de gaps; Eliminación de superposiciones; Validación final y Logs estructurados para depuración y seguimiento. Con estas mejoras, el script se convertiría en un pipeline CI/CD para datos espaciales, más completo y automatizado.&lt;/p&gt;

&lt;h3&gt;
  
  
  Requisitos para replicar esta solución
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.python.org/" rel="noopener noreferrer"&gt;Python 3.6+&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://qgis.org/" rel="noopener noreferrer"&gt;QGIS Desktop&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pypi.org/project/toolbox-sdk/" rel="noopener noreferrer"&gt;NextGIS Toolbox SDK&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://toolbox.nextgis.com/" rel="noopener noreferrer"&gt;API key de toolbox.nextgis.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reflexiones finales
&lt;/h3&gt;

&lt;p&gt;Vimos cómo combinar herramientas en la nube y locales con Python nos permite resolver problemas topológicos complejos de forma automatizada. El pipeline parte de una capa con gaps y bordes no coincidentes (sin coalición). El resultado es una capa donde los bordes adyacentes comparten exactamente los mismos vértices, eliminando gaps y logrando una buena coalición entre polígonos. &lt;/p&gt;

&lt;h3&gt;
  
  
  Claves:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Comprender la naturaleza de cada error (validez vs. topología).&lt;/li&gt;
&lt;li&gt;Aplicar las herramientas en el orden correcto.&lt;/li&gt;
&lt;li&gt;Elegir una tolerancia adecuada.&lt;/li&gt;
&lt;li&gt;Mantener el mismo número de features, no romper la integridad de la capa.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El resultado es una capa lista para análisis posteriores, y todo con menos de 100 líneas de Python.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enlaces
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Repositorio del script en GitHub&lt;br&gt;
&lt;a href="https://github.com/matkoclaudio/antigaps" rel="noopener noreferrer"&gt;https://github.com/matkoclaudio/antigaps&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Documentación de QGIS Processing&lt;br&gt;
&lt;a href="https://docs.qgis.org/" rel="noopener noreferrer"&gt;https://docs.qgis.org/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SDK de NextGIS Toolbox&lt;br&gt;
&lt;a href="https://nextgis.es/blog/nextgis-toolbox-sdk/" rel="noopener noreferrer"&gt;https://nextgis.es/blog/nextgis-toolbox-sdk/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NextGIS Toolbox SDK&lt;br&gt;
&lt;a href="https://toolbox.nextgis.com/" rel="noopener noreferrer"&gt;https://toolbox.nextgis.com/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>geoinformatics</category>
      <category>topology</category>
      <category>qgis</category>
      <category>nextgis</category>
    </item>
  </channel>
</rss>
