<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>GIS &#38; Chips &#187; josetomas</title>
	<atom:link href="http://www.gisandchips.org/author/josetomas/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.gisandchips.org</link>
	<description>Geografía útil para llevar</description>
	<lastBuildDate>Tue, 29 Nov 2011 10:38:56 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Proveedor de datos SQLite</title>
		<link>http://www.gisandchips.org/2011/11/13/proveedor-de-datos-spatialite/</link>
		<comments>http://www.gisandchips.org/2011/11/13/proveedor-de-datos-spatialite/#comments</comments>
		<pubDate>Sat, 12 Nov 2011 22:25:15 +0000</pubDate>
		<dc:creator>josetomas</dc:creator>
				<category><![CDATA[Programación]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Mono]]></category>
		<category><![CDATA[SpatiaLite]]></category>
		<category><![CDATA[SQLite]]></category>

		<guid isPermaLink="false">http://www.gisandchips.org/?p=1886</guid>
		<description><![CDATA[Si habéis intentado alguna vez acceder con C# a una base de datos SQLite, ya sabréis que existe un proveedor ADO.NET de código abierto, System.Data.SQLite. Sin embargo, a los que trabajamos en Linux, Mono ya nos ofrece un proveedor integrado en la propia plataforma (Mono.Data.Sqlite), que de hecho deriva del anterior. Mejor aún, las distros [...]]]></description>
			<content:encoded><![CDATA[<p align="justify">Si habéis intentado alguna vez acceder con C# a una base de datos <a href="http://www.sqlite.org/">SQLite</a>, ya sabréis que existe un proveedor ADO.NET de código abierto, <a href="http://system.data.sqlite.org">System.Data.SQLite</a>. Sin embargo, a los que trabajamos en Linux, Mono ya nos ofrece un proveedor integrado en la propia plataforma (<a href="http://www.mono-project.com/SQLite">Mono.Data.Sqlite</a>), que de hecho deriva del anterior. Mejor aún, las distros más populares incorporan SQLite por defecto. Y si, además, hablamos de una geodatabase, no hay ningún problema: hay paquetes de <a href="http://www.gaia-gis.it/spatialite/">SpatiaLite</a> a nuestra disposición. Todo parece un camino de rosas hasta que, a pesar de tenerlo todo felizmente instalado, uno se encuentra con que no es posible cargar la librería de SpatiaLite: al ejecutar el comando &#8220;SELECT load_extension(&#8216;libspatialite.so.2&#8242;)&#8221; obtenemos un lacónico &#8220;Not authorized&#8221;. Afortunadamente hay soluciones. La que aquí os propongo consiste simplemente en que uséis el proveedor Org.Gisandchips.Sqlite.</p>
<p><span id="more-1886"></span></p>
<h2>El problema de las extensiones de SQLite</h2>
<p align="justify">SpatiaLite es fundamentalmente una librería (libspatialite) que actúa como extensión de SQLite: explota el índice R-Tree de SQLite y añade el tipo Geometry y una serie de funciones que a su vez invocan a las de GEOS y PROJ. El resultado, al igual que el tándem PostgreSQL / PostGIS, es una implementación de la especificación &#8220;Simple Features for SQL&#8221; del OGC pero sin necesidad de una infraestructura cliente-servidor. Este tipo de librerías, que contienen funciones que extienden las propias de SQLite, se cargan dinámicamente, es decir, es el desarrollador el encargado de enlazarlas en tiempo de ejecución en el ámbito de una conexión de base de datos. El problema es que este mecanismo de extensión constituye a su vez un agujero de seguridad, un &#8220;coladero&#8221; de software malicioso. Así que, por defecto, las distros de Linux proporcionan el binario de SQLite compilado de forma que no se autoriza la carga dinámica de extensiones. Supongo que al final, quienes lo necesitan, optan por compilar su propio binario de SQLite o utilizar el que proporciona SpatiaLite, donde la extensión está estáticamente enlazada. Los que trabajáis con Mono tenéis ahora otra opción más.</p>
<h2>Org.Gisandchips.Sqlite</h2>
<p align="justify">Podéis descargar el código fuente de este proveedor ADO.NET para SQLite desde el repositorio SVN de Gis&amp;Chips:</p>
<p><code>svn co http://www.gisandchips.org/svn/sqliteprovider</code></p>
<p align="justify">Para vuestra comodidad, os proporciono el fichero de proyecto MonoDevelop. Yo compilo sin problemas en una máquina Ubuntu 11.04, con Mono 2.10, MonoDevelop 2.6 y bajo el framework .NET 4.</p>
<p align="justify">Si echáis un vistazo al código comprobaréis que se trata del mismo proveedor Mono.Data.Sqlite (obtenido de la master branch del repositorio público de Mono en github) con los siguientes añadidos:</p>
<ul>
<li>Wrappers para las funciones enable_load_extension() and load_extension() del API de sqlite3.</li>
<li>Método público SqliteConnection.LoadExtension() que permite cargar dinámicamente extensiones de sqlite3.</li>
</ul>
<p align="justify">El siguiente código de ejemplo es suficientemente explicativo:</p>
<pre class="brush: csharp; title: ; notranslate">
using (var cn = new SqliteConnection (&quot;Data Source=path/file.sqlite&quot;)) {
  cn.Open ();
  cn.LoadExtension (&quot;libspatialite.so.2&quot;);
  //your sql commands follow
}
</pre>
<p align="justify">Respecto a la licencia, me remito a la de Mono (MIT), que es la más permisiva y entiendo que compatible con las instrucciones del autor original de System.Data.SQLite, Robert Simpson, quien liberó este proveedor como de dominio público. Que lo disfrutéis.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gisandchips.org/2011/11/13/proveedor-de-datos-spatialite/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Paralelización con SIMD</title>
		<link>http://www.gisandchips.org/2011/10/20/paralelizacion-con-simd/</link>
		<comments>http://www.gisandchips.org/2011/10/20/paralelizacion-con-simd/#comments</comments>
		<pubDate>Thu, 20 Oct 2011 16:31:37 +0000</pubDate>
		<dc:creator>josetomas</dc:creator>
				<category><![CDATA[Análisis]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[Mono]]></category>
		<category><![CDATA[Mono.Simd]]></category>

		<guid isPermaLink="false">http://www.gisandchips.org/?p=1744</guid>
		<description><![CDATA[En este artículo os presento un test en el que medimos los tiempos que un programa escrito en C# emplea para procesar una serie de modelos digitales del terreno. El objetivo final es comparar el rendimiento de un código que hace un cálculo secuencial frente a otro que hace uso de una tecnología de paralelización [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: justify">En este artículo os presento un test en el que medimos los tiempos que un programa escrito en C# emplea para procesar una serie de modelos digitales del terreno. El objetivo final es comparar el rendimiento de un código que hace un cálculo secuencial frente a otro que hace uso de una tecnología de paralelización de datos por hardware denominada SIMD, disponible en la mayoría de microprocesadores que usamos hoy en día. Y el resultado es muy interesante.<div id="attachment_1801" class="wp-caption aligncenter" style="width: 310px"><a href="http://www.gisandchips.org/wp-content//SIMD_performance.png"><img src="http://www.gisandchips.org/wp-content//SIMD_performance-300x131.png" alt="SIMD performance test" width="300" height="131" class="size-medium wp-image-1801" /></a><p class="wp-caption-text">Test de rendimiento SIMD con Mono.Simd.Vector8s</p></div><span id="more-1744"></span></p>
<p style="text-align: justify">Las técnicas de computación paralela encuentran en los sistemas de información geográfica una de sus aplicaciones más elocuentes dada la naturaleza masiva de los datos geográficos, en particular si hablamos de estructuras de datos raster. No en vano, allá por el año 1997, Richard Healey, junto con otros compañeros del Dpto. de Geografía de la Universidad de Edimburgo, editó &#8220;Parallel Processing Algorithms For GIS&#8221;. El profesor Healey es un geógrafo que ya a principios de los 90 planteaba a sus alumnos problemas de geoprocesamiento paralelo que éstos habían de resolver con ADA, el único lenguaje accesible que por aquel entonces prefiguraba el paradigma OOP y permitía definir tareas concurrentes. Desde entonces se han producido muchos avances en paralelización de procesos, tanto a nivel de software como de hardware. Sin embargo, existe un tipo de paralelización que no parece haber alcanzado demasiada notoriedad en el campo de los GIS: se trata del conjunto de instrucciones <a href="http://en.wikipedia.org/wiki/SIMD">SIMD</a> (Single Instruction, Multiple Data) que permite realizar operaciones aritméticas entre pares de vectores mediante una sola instrucción. No se trata por tanto de concurrencia de procesos, sino de paralelismo a nivel de datos. Lo más curioso es que, desde 1996, los fabricantes de microprocesadores han ido incorporando extensiones que soportan en mayor o menor medida este juego de instrucciones.</p>
<p style="text-align: justify">Cualquier PC o videoconsola actual permite ejecutar instrucciones SIMD. Evidentemente los desarrolladores de juegos hace tiempo que explotan este tipo de paralelización. Sin embargo, encontrar referencias recientes a SIMD en el ámbito de la Geomática resulta más complicado: <a href="http://gisws.media.osaka-cu.ac.jp/grass04/viewpaper.php?id=15">aquí tenéis una interesante ponencia</a> de la conferencia de usuarios de GRASS del 2004. Seguramente habrá más trabajos publicados, así que hacednos saber lo que encontréis.</p>
<p style="text-align: justify">Yo sinceramente no sabía nada de esto hasta que leí <a href="http://tirania.org/blog/archive/2008/Nov-03.html">este post de Miguel de Icaza</a>, anunciando la publicación de <a href="http://docs.go-mono.com/monodoc.ashx?link=N%3aMono.Simd">Mono.Simd</a>. Este API es un paso en la estrategia de Mono por entrar de lleno en la industria del &#8220;gaming&#8221;. ¿Y para las desarrolladores de GIS? En mi opinión significa un abanico de posibilidades a la hora de paralelizar geoprocesos que queda pendiente de explorar.</p>
<p style="text-align: justify">En este artículo sólo quiero dejar constancia de un test para un caso extremo de optimización con SIMD. En la práctica carece de utilidad, mi única intención es medir diferencias de tiempo en un escenario absolutamente favorable a la paralelización mediante vectores. Dado un grid de elevaciones, donde cada valor de altitud puede representarse como un entero de 16 bits, se trata simplemente de doblar el valor de cada pixel para &#8220;exagerar&#8221; el modelo del terreno. Estas son las especificaciones de la máquina en que he realizado el test:</p>
<ul>
<li>Intel Q8300 @ 2.50GHz</li>
<li>3.9 GiB RAM</li>
<li>Ubuntu 11.04 i686</li>
<li>Mono 2.10.5</li>
</ul>
<p style="text-align: justify">El test se ha realizado sobre 4 ficheros <a href="http://en.wikipedia.org/wiki/Esri_grid">ARC/INFO ASCII GRID</a> de distintos tamaños. Para cada fichero se han tomado 5 muestras del tiempo transcurrido en el cálculo aritmético sin aceleración de hardware, pixel a pixel, tomando finalmente la mediana. La misma metodología de muestreo se ha empleado para obtener los tiempos con aceleración de hardware, es decir, empleando Mono.Simd para multiplicar los pixels de 8 en 8 mediante una única instrucción. Para ello es necesario agrupar previamente los valores de altitud y almacenar cada vector en una lista de tipo Mono.Simd.Vector8s. Estos son los resultados:</p>
<table style="height: 5px;width: 80%" border="1" cellspacing="1" cellpadding="1" align="center">
<thead>
<tr>
<th scope="col"><span class="Apple-style-span" style="font-weight: normal">File size (MiB)</span></th>
<th scope="col"><span class="Apple-style-span" style="font-weight: normal">Elapsed time (miliseconds)</span></th>
<th scope="col"><span class="Apple-style-span" style="font-weight: normal">Elapsed time with SIMD (miliseconds)</span></th>
<th scope="col"><span class="Apple-style-span" style="font-weight: normal">Time reduction</span></th>
<th scope="col"><span class="Apple-style-span" style="font-weight: normal">Performance gain</span></th>
</tr>
</thead>
<tbody>
<tr>
<td>12.0</td>
<td>49.7447</td>
<td>14.2542</td>
<td>71.3</td>
<td><span style="background-color: #ffd700"><strong>3.5x</strong></span></td>
</tr>
<tr>
<td>48.2</td>
<td>197.5251</td>
<td>56.149</td>
<td>71.6</td>
<td><span style="background-color: #ffd700"><strong>3.5x</strong></span></td>
</tr>
<tr>
<td>192.7</td>
<td>783.0778</td>
<td>216.305</td>
<td>72.4</td>
<td><span style="background-color: #ffd700"><strong>3.6x</strong></span></td>
</tr>
<tr>
<td>535.4</td>
<td>2180.8665</td>
<td>618.2225</td>
<td>71.7</td>
<td><span style="background-color: #ffd700"><strong>3.5x</strong></span></td>
</tr>
</tbody>
</table>
<p style="text-align: justify">Como veis, en todos los casos, con SIMD el proceso de cálculo se completa en torno a 3,5 veces más rápido. Esto es una buena noticia, pero no significa que este tipo de paralelismo sea la panacea. En mi opinión la conclusión es que, si con un escenario favorable se puede reducir el tiempo de proceso en más de un 70%, merece la pena explorar otros escenarios con verdadera utilidad práctica.</p>
<p style="text-align: justify">Si alguien se pregunta por la incidencia que puede tener esta mejora en el rendimiento si contabilizamos el proceso de lectura y carga en memoria de los datos, me atrevería a decir que escasa. En cualquier caso, los procesos de lectura y manejo de grandes volúmenes de información raster constituyen un problema distinto y existen diversas técnicas para su optimización.</p>
<p style="text-align: justify">Si os pica la curiosidad, podeis averiguar el conjunto de instrucciones SIMD que soporta vuestro procesador ejecutando el código de ejemplo que aparece en la documentación de la clase <a href="http://docs.go-mono.com/monodoc.ashx?link=T%3aMono.Simd.SimdRuntime">Mono.Simd.SimdRuntime</a> (es posible que tengáis que hacer algunas modificaciones). Para aquellos que quieran reproducir un test de rendimiento con sus propios ficheros ASCII GRID, aquí os dejo el código C# que he usado (por cierto, ni es óptimo ni puedo garantizar que esté libre de bugs):</p>
<pre class="brush: csharp; title: ; notranslate">
using System;
using System.IO;
using Mono.Simd;
using System.Collections.Generic;
using System.Globalization;
using System.Diagnostics;
using System.Linq;

namespace SIMDTest
{
 class MainClass
 {
  public static void Main (string[] args)
  {
   bool useSIMD = true;
   string path = string.Empty;
   CultureInfo formatProvider = null;
   try {
    if (args.Length == 0) {
     Console.WriteLine (&quot;Usage: SIMDTest [ASC GRID file name] [specific culture name]&quot;);
     return;
    }
    if (args.Length &gt; 0)
     path = args[0];
    if (args.Length &gt; 1)
     formatProvider = CultureInfo.GetCultureInfo (args[1]);
    Console.WriteLine (&quot;Use hardware acceleration? [y / n]? : &quot;);
    ConsoleKeyInfo key = Console.ReadKey ();
    while (key.Key != ConsoleKey.Y &amp;&amp; key.Key != ConsoleKey.N) {
     Console.WriteLine (&quot;Press 'y' to use hardware acceleration, 'n' otherwise: &quot;);
     key = Console.ReadKey ();
    }
    Console.WriteLine ();
    Console.WriteLine (&quot;Processing, please wait ...&quot;);
    useSIMD = (key.Key == ConsoleKey.Y);
    ElevationModel dem = new ElevationModel(path, formatProvider);
    Stopwatch stopwatch = new Stopwatch ();
    if (useSIMD) {
     Vector8s scale = new Vector8s (2);
     foreach (IList vPage in dem.EnumerateVectors ()) {
      stopwatch.Start ();
      for (int i = 0; i &lt; vPage.Count; i ++)
       vPage[i] = vPage[i] * scale;
      stopwatch.Stop ();
     }
    } else {
     foreach (IList page in dem.EnumerateData ()) {
      stopwatch.Start ();
      for (int i = 0; i &lt; page.Count; i ++)
       page[i] = (short) (page[i] * 2);
      stopwatch.Stop ();
     }
    }
    Console.WriteLine (&quot;Time elapsed: {0}&quot;, stopwatch.Elapsed);
   } catch (Exception ex) {
    Console.WriteLine (ex.Message);
   }
  }
 }

 class ElevationModel
 {
  string _path;
  IFormatProvider _formatProvider = CultureInfo.CurrentCulture;
  short _noData = -9999;
  int BOD = 0;

  public ElevationModel (string path, IFormatProvider formatProvider)
  {
   if (string.IsNullOrEmpty (path))
    throw new ArgumentNullException ();
   else if (!File.Exists (path))
    throw new FileNotFoundException ();
   else {
    _path = path;
    if (formatProvider != null)
     _formatProvider = formatProvider;
    this.SetMetadata ();
   }
  }

  public ElevationModel (string path) : this(path, null)
  {
  }

  void SetMetadata ()
  {
   using (StreamReader reader = new StreamReader (_path)) {
    //skip GRID header
    for (int i = 0; i &lt; 5; i++)
     reader.ReadLine ();
    _noData = short.Parse (reader.ReadLine ().Split (' ')[1], _formatProvider);
    reader.BaseStream.Position = 0;
    reader.DiscardBufferedData ();
    char[] buffer = new char[512];
    reader.Read(buffer, 0, buffer.Length);
    BOD = this.GetBeginOfData (buffer);
   }
  }

  int GetBeginOfData (char[] source)
  {
   int i = 0;
   bool isNewHeaderLine = false;
   bool isNewLine = true;
   foreach (char c in source) {
    if (char.IsLetter (c) &amp;&amp; isNewLine) {
     isNewHeaderLine = true;
     isNewLine = false;
    } else if ((char.IsDigit (c) || c == '-') &amp;&amp; isNewLine &amp;&amp; !isNewHeaderLine) {
     break;
    } else if (char.IsControl (c)) {
     isNewLine = true;
     isNewHeaderLine = false;
    }
    i++;
   }
   return i;
  }

  IList&lt;short&gt; GetPage (char[] source, int sourceIndex, int sourceLength, IList pendingDigits)
  {
   IList&lt;short&gt; page = new List&lt;short&gt; ();
   for (int i = sourceIndex; i &lt; sourceLength; i++) {
    if (char.IsDigit (source[i]) || source[i] == '-')
     pendingDigits.Add (source[i]);
    else if (pendingDigits.Count &gt; 0 &amp;&amp; (char.IsWhiteSpace (source[i]) || char.IsControl (source[i]))) {
     short n = _noData;
     string s = new string(pendingDigits.ToArray ());
     if (!short.TryParse(s, NumberStyles.Integer, _formatProvider, out n))
      Console.WriteLine (&quot;Error parsing {0}&quot;, s);
     page.Add (n);
     pendingDigits.Clear ();
    }
   }
   return page;
  }

  public IEnumerable&lt;IList&lt;short&gt;&gt; EnumerateData ()
  {
   using (StreamReader reader = new StreamReader (_path)) {
    char[] buffer = new char[25000 * 1024];
    int beginOfData = 0;
    IList pendingDigits = new List ();
    int charCount = 0;
    while ((charCount = reader.Read (buffer, 0, buffer.Length)) &gt; 0) {
     if (beginOfData == 0) {
      beginOfData = this.BOD;
      yield return this.GetPage (buffer, beginOfData, charCount, pendingDigits);
     } else
      yield return this.GetPage (buffer, 0, charCount, pendingDigits);
    }
   }
  }

  IList&lt;Vector8s&gt; GetVectorPage(IList page, IList pendingValues)
  {
   List vPage = new List ();
   foreach (short n in page) {
    pendingValues.Add (n);
    if (pendingValues.Count == 8 ) {
     vPage.Add (new Vector8s (pendingValues[0], pendingValues[1],
      pendingValues[2], pendingValues[3], pendingValues[4],
      pendingValues[5], pendingValues[6], pendingValues[7]));
     pendingValues.Clear ();
    }
   }
   return vPage;
  }

  public IEnumerable&lt;IList&lt;Vector8s&gt;&gt; EnumerateVectors ()
  {
   int lastPageSize = -1;
   IList pendingValues = new List ();
   foreach (IList page in this.EnumerateData ()) {
    IList vPage = this.GetVectorPage (page, pendingValues);
    // complete last vector in last page
    if (lastPageSize != -1 &amp;&amp; vPage.Count &lt; lastPageSize) {
     if (pendingValues.Count &gt; 0 &amp;&amp; pendingValues.Count &lt; 8 ) {
      for (int i = pendingValues.Count; i &lt; 8; i++)
	pendingValues.Add(_noData);
      vPage.Add (new Vector8s (pendingValues[0], pendingValues[1],
       pendingValues[2], pendingValues[3], pendingValues[4],
       pendingValues[5], pendingValues[6], pendingValues[7]));
     }
    }
    lastPageSize = vPage.Count;
    yield return vPage;
   }
  }
 }
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.gisandchips.org/2011/10/20/paralelizacion-con-simd/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Cómo evitar la inserción de geometrías duplicadas en PostgreSQL</title>
		<link>http://www.gisandchips.org/2009/12/22/como-evitar-la-insercion-de-geometrias-duplicadas-en-postgresql/</link>
		<comments>http://www.gisandchips.org/2009/12/22/como-evitar-la-insercion-de-geometrias-duplicadas-en-postgresql/#comments</comments>
		<pubDate>Tue, 22 Dec 2009 11:51:02 +0000</pubDate>
		<dc:creator>josetomas</dc:creator>
				<category><![CDATA[Consejo práctico]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[geometrías duplicadas]]></category>
		<category><![CDATA[PostGIS]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[RULE]]></category>

		<guid isPermaLink="false">http://www.gisandchips.org/?p=1171</guid>
		<description><![CDATA[Una de las tareas habituales en el tratamiento de inconsistencias cartográficas es la detección y corrección de geometrías duplicadas. En muchos casos eliminarlas es necesario para evitar incoherencias en nuestra base de datos geográfica, sobre todo en cuanto al cómputo de frecuencias, longitudes y superficies. Por otra parte la eliminación a posteriori, también plantea problemas [...]]]></description>
			<content:encoded><![CDATA[<p>Una de las tareas habituales en el tratamiento de inconsistencias cartográficas es la detección y corrección de geometrías duplicadas. En muchos casos eliminarlas es necesario para evitar incoherencias en nuestra base de datos geográfica, sobre todo en cuanto al cómputo de frecuencias, longitudes y superficies. Por otra parte la eliminación a posteriori, también plantea problemas de decisión que pueden complicar el código a desarrollar y desembocar en tiempos dilatados de proceso. De modo que, en ocasiones merece la pena plantearse la detección de geometrías duplicadas a priori. Implementar semejante mecanismo de control del lado del cliente puede resultar relativamente sencillo, pero lo que aquí os propongo es centralizar la lógica y evitar la inserción de geometrías duplicadas desde el lado del servidor PostgreSQL/PostGIS.<span id="more-1171"></span></p>
<p>Planteado el problema, la primera solución que viene a la mente pasa por un &#8220;trigger&#8221; que haga la llamada a la correspondiente función de evaluación y lance un mensaje de error en caso necesario. Sin embargo, si lanzar el mensaje de error no es absolutamente necesario y lo que perseguimos es que una transacción de, por ejemplo, 1000 geometrías donde 50 son duplicadas, se complete con los 950 registros válidos insertados, entonces podemos usar una regla o <a title="RULE" href="http://www.postgresql.org/docs/8.4/static/rules.html" target="_blank">RULE</a>. Cada dos por tres estoy revisitando el sistema de reglas de PostgreSQL y no deja de sorprenderme por su versatilidad y elegancia. Echad un vistazo a la solución propuesta y decidid por vosotros mismos:</p>
<pre class="brush: sql; title: ; notranslate">
CREATE OR REPLACE RULE skip_duplicate AS
 ON INSERT TO table_name
 WHERE 0 != (SELECT count(*)
 FROM table_name
 WHERE geom_field ~= new.geom_field) DO INSTEAD NOTHING;
</pre>
<p>Como podéis comprobar, hay 2 claves para interpretar esta regla:</p>
<p>1) Es una regla condicional (CREATE RULE &#8230; <em>WHERE</em> &#8230; DO &#8230;), y en la condición integramos una subquery que emplea el operador de igualdad geométrica (<em>~=</em>) para evaluar si la geometría de entrada (<em>new.geom_field</em>) es un duplicado. Recordad que, para acelerar el proceso, los operadores de PostGIS explotan, si está presente, el índice GIST sobre el campo de geometría.</p>
<p>2) Con <em>INSTEAD NOTHING</em> indicamos al motor de reescritura de expresiones subyacente al sistema de reglas que, cuando se cumpla la condición anterior, simplemente vacíe la expresión entrante y no haga nada. Por tanto, la sentencia INSERT se omite y deja de formar parte de la transacción actual.</p>
<p>¡Eso es todo!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gisandchips.org/2009/12/22/como-evitar-la-insercion-de-geometrias-duplicadas-en-postgresql/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Edición de geometrías en NTS con CoordinateArrayFilter</title>
		<link>http://www.gisandchips.org/2009/11/04/edicion-de-geometrias-en-nts-con-coordinatearrayfilter/</link>
		<comments>http://www.gisandchips.org/2009/11/04/edicion-de-geometrias-en-nts-con-coordinatearrayfilter/#comments</comments>
		<pubDate>Wed, 04 Nov 2009 11:09:09 +0000</pubDate>
		<dc:creator>josetomas</dc:creator>
				<category><![CDATA[Consejo práctico]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[CoordinateArrayFilter]]></category>
		<category><![CDATA[JTS]]></category>
		<category><![CDATA[NTS]]></category>

		<guid isPermaLink="false">http://www.gisandchips.org/?p=747</guid>
		<description><![CDATA[Java Topology Suite (JTS) es una librería que resuelve problemas complejos y en la que sus arquitectos han recurrido al uso exhaustivo de patrones de diseño de software como medio de gestionar eficientemente dicha complejidad. Como muestra baste mencionar que los desarrolladores debemos asumir desde el principio el factory pattern que implementa la clase GeometryFactory [...]]]></description>
			<content:encoded><![CDATA[<p><a title="Java Topology Suite" href="http://www.vividsolutions.com/jts/JTSHome.htm" target="_blank">Java Topology Suite</a> (JTS) es una librería que resuelve problemas complejos y en la que sus arquitectos han recurrido al uso exhaustivo de <a title="patrones de diseño de software" href="http://en.wikipedia.org/wiki/Design_pattern_(computer_science)" target="_blank">patrones de diseño de software</a> como medio de gestionar eficientemente dicha complejidad. Como muestra baste mencionar que los desarrolladores debemos asumir desde el principio el <a title="factory pattern" href="http://en.wikipedia.org/wiki/Factory_pattern" target="_blank"><em>factory pattern</em></a> que implementa la clase <em>GeometryFactory</em> que empleamos en la construcción de objetos geométricos. El hecho de que JTS haga uso de patrones de diseño es una muestra más de su fiabilidad, aunque en ocasiones (sobretodo si hay carencia de documentación) también implica un esfuerzo extra por nuestra parte a la hora de entender cómo se solucionan determinados casos de uso. Uno de esos casos en los que la solución no se evidencia a primera vista es la edición de los vértices de una geometría, así que en este  artículo os muestro código de ejemplo para el porting de JTS a C#, <a title="NetTopologySuite" href="http://code.google.com/p/nettopologysuite/" target="_blank">NetTopologySuite</a> (NTS) en su versión 1.2, que os permitirá introduciros en el manejo del interface <em>ICoordinateFilter </em>para la manipulación geométrica.<span id="more-747"></span></p>
<p>Quizá me equivoque, pero creo que no se puede calificar JTS/NTS como un &#8220;framework&#8221; para el desarrollo de herramientas de  manipulación de geometrías (e.g. aplicaciones tipo CAD). El diseño de este API promueve más bien la idea de inmutabilidad de un objeto geométrico a lo largo de su ciclo de vida (aunque no hay un mecanimo que fuerze dicha inmutabilidad), y se centra en la modelización a bajo nivel de las tareas de cálculo topológico y análisis espacial, y en la exactitud de los resultados derivados de un geoproceso, todo ello en función de las recomendaciones del <a title="OGC Simple Feature Access specification" href="http://www.opengeospatial.org/standards/sfa" target="_blank">Open Geospatial Consortium</a>.</p>
<p>Pero es probable que, en algún punto de una secuencia de geoprocesamiento, necesitemos manipular las coordenadas de una geometría (por ejemplo, forzar una corrección de forma transparente para el  usuario). Asumiendo que el array de coordenadas almacena la secuencia de vértices actual en lugar de una copia (que la propiedad <em>Coordinates </em>sea o no una copia depende de la secuencia de coordenadas empleada en la construcción de la geometría; tenéis más información sobre esto en el capítulo 8 de la <a title="JTS Developer's Guide" href="http://www.vividsolutions.com/jts/bin/JTS%20Developer%20Guide.pdf" target="_blank">guía del desarrollador de JTS</a>), nada nos impide editar diréctamente los valores en ese array. También podríamos copiar dicho array, operar sobre los vértices en cuestión, instanciar una nueva geometría y desechar la antigua. Discutir sobre la idoneidad de estas soluciones no es objeto de este artículo. Lo que interesa es que sepamos que el propio API proporciona otras vías específicas para la edición de vértices.</p>
<p>En concreto, si tan sólo necesitamos operar con las coordenadas existentes (no añadir ni borrar), NTS proporciona el interfaz <em>ICoordinateFilter</em>, a partir del cual pueden derivarse clases que implementen el <a title="visitor pattern" href="http://en.wikipedia.org/wiki/Visitor_pattern"><em>visitor pattern</em></a> para operar con la lista de coordenadas de cualquier tipo de geometría. Cada desarrollador puede implementar sus propias operaciones o filtros de coordenadas a conveniencia, pero NTS ya proporciona unos cuantos: <em>UniqueCoordinateArrayFilter</em>, <em>CoordinateCountFilter </em>y, el más simple y objeto de nuestro interés,  <em>CoordinateArrayFilter</em>, que da acceso al array completo de coordenadas para su manipulación.</p>
<h2>Veamos un ejemplo</h2>
<p>Suponed que tenemos el siguiente LineString:</p>
<pre>LINESTRING (100 100,  150 180,  200 210,
            210 140,  100 110,  100 100)</pre>
<p>Si lo visualizárais en vuestro cliente favorito observaríais algo similar a esto:</p>
<p style="padding-left: 90px"><img class="size-medium wp-image-632" src="http://www.gisandchips.org/wp-content/CoordinateArrayFilter1-300x222.png" alt="LineString con autointersección" width="300" height="222" /></p>
<p>Esta geometría presenta una autointersección que NTS permite detectar de diversas formas. Nosotros nos centraremos en corregirla aplicando un criterio muy simple: intercambiaremos los valores de las coordenadas en las posiciones 0 y 4. Aquí tenéis el código fuente de esta operación de desplazamiento de vértices:</p>
<pre class="brush: csharp; title: ; notranslate">
using System;
using GisSharpBlog.NetTopologySuite.Geometries;
using GisSharpBlog.NetTopologySuite.IO;
using GisSharpBlog.NetTopologySuite.Utilities;

namespace CoordFilter
{
 class Program
 {
  public static void Main(string[] args)
  {
   string wkt = &quot;LINESTRING (100 100,  150 180,  200 210, 210 140,  100 110,  100 100)&quot;;
   WKTReader r = new WKTReader();
   Geometry g = r.Read(wkt);
   //Creamos sendas copias de las coordenadas objeto de desplazamiento
   Coordinate c0 = new Coordinate(g.Coordinates[0]);
   Coordinate c4 = new Coordinate(g.Coordinates[4]);
   //Instanciamos el filtro; puesto que queremos acceso a todo el array
   //de coordenadas de nuestra geometría, indicamos el tamaño del array
   CoordinateArrayFilter filter = new CoordinateArrayFilter(g.NumPoints);
   //El método Apply vuelca el array de coordenadas de la geometría en
   //el array de coordenadas del filtro
   g.Apply(filter);
   //Realizamos la operación de desplazamiento de vértices
   //sobre el array de coordenadas del filtro
   filter.Coordinates[0].X = c4.X;
   filter.Coordinates[0].Y = c4.Y;
   filter.Coordinates[4].X = c0.X;
   filter.Coordinates[4].Y = c0.Y;
   filter.Coordinates[5].X = c4.X;
   filter.Coordinates[5].Y = c4.Y;
   //Notificamos la actualización para que el Envelope pueda recalcularse
   g.GeometryChanged();
   Console.WriteLine(g.ToText());
   Console.Write(&quot;Press any key to continue . . . &quot;);
   Console.ReadKey(true);
  }
 }
}
</pre>
<p>Si ejecutáis el código, obtendréis el siguiente resultado en WKT:</p>
<pre>LINESTRING (100 110,  150 180,  200 210,
            210 140,  100 100,  100 110)</pre>
<p>Y este sería el resultado gráfico:</p>
<p style="padding-left: 90px"><img class="size-medium wp-image-657" src="http://www.gisandchips.org/wp-content/CoordinateArrayFilter2-300x221.png" alt="LineString modificado con CoordinateArrayFilter" width="300" height="221" /></p>
<h2>¿Qué hay del modelo de precisión?</h2>
<p>Lo cierto es que la clase CoordinateArrayFilter no toma en consideración el modelo de precisión de la geometría a modificar. Entonces ¿cómo podemos controlar que, ante un desplazamiento, las coordenadas se calculen sobre el &#8220;grid&#8221; de referencia asociado a la geometría? Una opción es utilizar la clase <em>GeometryEditor</em>, lo que implica generar una nueva geometría a partir de la antigua. Pero ya que hablamos de filtros y traslado de coordenadas, ¿por qué no derivar una clase de tipo ICoordinateFilter que, dado un desplazamiento (dx, dy), mueva una geometría al completo respetando su modelo de precisión? Aquí tenéis un ejemplo:</p>
<pre class="brush: csharp; title: ; notranslate">
using System;
using GisSharpBlog.NetTopologySuite.Geometries;
using GisSharpBlog.NetTopologySuite.IO;

namespace CoordFilter
{
 class Program
 {
  public static void Main(string[] args)
  {
   string wkt = &quot;LINESTRING (100.24 100.5, 150.82 180.56, 200.06 210.1, 210.44 140.82, 100.24 100.5)&quot;;
   //Asumiendo que la unidad principal es el metro, asociamos un &quot;grid&quot; centimétrico
   //a nuestro objeto GeometryFactory.
   WKTReader r = new WKTReader(new GeometryFactory(new PrecisionModel(100)));
   Geometry g = r.Read(wkt);
   //Instanciamos el filtro; puesto que queremos acceso a todo el array
   //de coordenadas de la geometría a desplazar, indicamos el tamaño del array;
   //además indicamos el modelo de precisión de dicha geometría.
   GeometryMoveFilter filter = new GeometryMoveFilter(g.NumPoints, g.Factory.PrecisionModel);
   g.Apply(filter);
   filter.Move(5.342237891, 3.865754133);
   g.GeometryChanged();
   Console.WriteLine(g.ToText());
   Console.Write(&quot;Press any key to continue . . . &quot;);
   Console.ReadKey(true);
  }

  //Clase derivada de ICoordinateFilter para el desplazamiento
  //de geometrías acorde con un modelo de precisión. El código de esta
  //clase está basado en el de la clase CoordinateArrayFilter de NTS,
  //cuyo autor es Diego Guidi.
  class GeometryMoveFilter : ICoordinateFilter
  {
   Coordinate[] pts = null;
   int n = 0;
   PrecisionModel grid = null;

   //Al igual que la clase CoordinateArrayFilter, el constructor necesita
   //el tamaño del array de coordenadas. Adicionalmente se necesita el
   //objeto PrecisionModel que define el &quot;grid&quot; de referencia
   public GeometryMoveFilter(int size, PrecisionModel model)
   {
    pts = new Coordinate[size];
    grid = model;
   }

   //Este método aplica un desplazamiento en X y en Y sobre
   //cada coordenada
   public void Move(double xOffset, double yOffset)
   {
    for(int i = 0; i &lt; n; i++)
    {
     Coordinate c = pts[i];
     //Aplicamos el desplazamiento
     c.X += xOffset;
     c.Y += yOffset;
     //Ajustamos la coordenada al &quot;grid&quot; de referencia
     grid.MakePrecise(ref c);
    }
   }

   public void Filter(Coordinate coord)
   {
    pts[n++] = coord;
   }
  }
 }
}
</pre>
<p>Si lo ejecutáis deberíais obtener el siguiente resultado:</p>
<p>LINESTRING (105.58 104.37, 156.16 184.43, 205.4 213.97, 215.78 144.69, 105.58 104.37)</p>
<p>Recordad que si estáis trabajando con NTS 1.2 hay que solucionar previamente un &#8220;bug&#8221; que afecta a la clase <em>PrecisionModel</em>. En <a title="modelos de precisión fija en NTS" href="http://www.gisandchips.org/2009/11/03/como-definir-y-usar-modelos-de-precision-fija-en-nts/" target="_blank">este artículo</a> encontraréis información sobre cómo resolverlo.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gisandchips.org/2009/11/04/edicion-de-geometrias-en-nts-con-coordinatearrayfilter/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Cómo definir y usar modelos de precisión fija en NTS</title>
		<link>http://www.gisandchips.org/2009/11/03/como-definir-y-usar-modelos-de-precision-fija-en-nts/</link>
		<comments>http://www.gisandchips.org/2009/11/03/como-definir-y-usar-modelos-de-precision-fija-en-nts/#comments</comments>
		<pubDate>Tue, 03 Nov 2009 13:14:29 +0000</pubDate>
		<dc:creator>josetomas</dc:creator>
				<category><![CDATA[Consejo práctico]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[fixed PrecisionModel]]></category>
		<category><![CDATA[JTS]]></category>
		<category><![CDATA[MakePrecise]]></category>
		<category><![CDATA[NTS]]></category>

		<guid isPermaLink="false">http://www.gisandchips.org/?p=470</guid>
		<description><![CDATA[En JTS Topology Suite el modelo de precisión  es un mecanismo central que determina la robustez de los cálculos geométricos y topológicos. En las especificaciones técnicas de este API libre para el análisis espacial encontrareis una detallada exposición de las implicaciones del modelo de precisión, principalmente en lo que se refiere a situaciones de colapso [...]]]></description>
			<content:encoded><![CDATA[<p>En <a title="JTS Topology Suite" href="http://www.vividsolutions.com/JTS/JTSHome.htm" target="_blank">JTS Topology Suite</a> el modelo de precisión  es un mecanismo central que determina la robustez de los cálculos geométricos y topológicos. En las <a title="especificaciones técnicas" href="http://www.vividsolutions.com/jts/bin/JTS%20Technical%20Specs.pdf" target="_blank">especificaciones técnicas</a> de este API libre para el análisis espacial encontrareis una detallada exposición de las implicaciones del modelo de precisión, principalmente en lo que se refiere a situaciones de colapso dimensional. Por ejemplo, podemos encontrarnos una situación de colapso dimensional cuando a partir de una geometría topológicamente válida calculada sobre un modelo de doble precisión (i.e. un polígono calculado mediante un objeto <em>Polygonizer</em>) obtenemos su representación en WKT y al intentar renderizarlo en una aplicación externa obtenemos un error de validación topológica (i.e. el polígono contiene una autointersección).</p>
<p>Cuando nos vemos en esta coyuntura, es decir, en la necesidad de transferir geometría entre aplicaciones mediante un formato interoperable pero de precisión limitada (i.e. DXF, WKT), hay que plantearse el uso de un modelo de precisión fija, a saber, un modelo que nos permita definir un &#8220;grid&#8221; al que siempre estarán referidas nuestras coordenadas, tanto las de origen como las derivadas del cálculo. En este breve artículo tenéis ejemplos de código fuente que os orientarán sobre cómo aplicar la clase <em>PrecisionModel</em> para trabajar con un &#8220;grid&#8221; de referencia en <a title="NetTopologySuite" href="http://code.google.com/p/nettopologysuite/" target="_blank">NetTopologySuite</a> (NTS), el porting de JTS a C#, en su versión 1.2.<span id="more-470"></span></p>
<p>Definir un &#8220;grid&#8221; es sencillo. Basta con crear una instancia de la clase <em>PrecisionModel</em> y asignarlo al objeto <em>GeometryFactory</em> mediante el cual construyamos nuestras geometrías. Cada geometría así instanciada heredará una referencia a un &#8220;grid&#8221; que determinará cualquier cálculo en el que dicha geometría intervenga posteriormente. Pero, ¿cómo se especifica la precisión de dicho  &#8220;grid&#8221;? Asignando un <strong>factor de escala</strong> que equivale al denominador de la fracción deseada de la unidad principal. Por ejemplo, si la unidad principal es el metro asignaremos 100 para trabajar en centímetros (coordenadas con un máximo de 2 decimales), 1000 para trabajar en milímetros (coordenadas con un máximo de 3 decimales), 200 para trabajar con una precisión de 0.5 centímetros, &#8230;</p>
<pre class="brush: cpp; title: ; notranslate">
using System;
using GisSharpBlog.NetTopologySuite.Geometries;

namespace Grids
{
 class Program
 {
  public static void Main(string[] args)
  {
   //Definición de varios &quot;grids&quot; asumiendo que trabajamos en metros
   PrecisionModel mGrid = new PrecisionModel(PrecisionModels.Fixed); //precisión métrica
   PrecisionModel mGrid2 = new PrecisionModel(1); //equivale al anterior
   PrecisionModel cmGrid = new PrecisionModel(100); //precisión centimétrica
   PrecisionModel mmGrid = new PrecisionModel(1000); //precisión milimétrica
   PrecisionModel halfcmGrid = new PrecisionModel(200); //0.5 cm de precisión
  }
 }
}
</pre>
<p>Cuando se parte de geometrías expresadas como <a title="WKT" href="http://en.wikipedia.org/wiki/Well-known_text" target="_blank">WKT</a>, la aplicación del &#8220;grid&#8221; a la imagen en memoria de las nuevas geometrías es transparente para el programador. Basta con asignar al objeto <em>WKTReader</em> el correspondiente objeto <em>GeometryFactory</em>. Esto significa que si probáis este código:</p>
<pre class="brush: cpp; title: ; notranslate">
using System;
using GisSharpBlog.NetTopologySuite.Geometries;
using GisSharpBlog.NetTopologySuite.IO;

namespace Grids
{
 class Program
 {
  public static void Main(string[] args)
  {
   string wkt = &quot;POLYGON ((253722.078898558 4292685.50730101, 253722.07889856 4292685.50730102, 253735.282733042 4292698.56531273, 253738.905778592 4292696.94218745, 253741.840165249 4292695.62758151, 253735.276565443 4292675.86615228, 253717.38 4292636.98, 253700.54 4292646.23, 253722.078898558 4292685.50730101))&quot;;
   PrecisionModel grid = new PrecisionModel(1000); //precisión milimétrica
   GeometryFactory gf = new GeometryFactory(grid); //vinculamos el &quot;grid&quot; con un objeto GeometryFactory
   WKTReader r = new WKTReader(gf); //asociamos el objeto GeometryFactory con el intérprete de WKT
   Console.WriteLine(r.Read(wkt).ToText()); //de WKT a Geometry y de nuevo a WKT
   Console.Write(&quot;Press any key to continue . . . &quot;);
   Console.ReadKey(true);
  }
 }
}
</pre>
<p>Éste es el resultado que deberíais obtener:</p>
<pre>POLYGON ((253722.079 4292685.507, 253722.079 4292685.507,
          253735.283 4292698.565, 253738.906 4292696.942,
          253741.84 4292695.628, 253735.277 4292675.866,
          253717.38 4292636.98, 253700.54 4292646.23,
          253722.079 4292685.507))</pre>
<p>Sin embargo, puede ocurrir que nuestros datos de entrada se presenten como lista de pares (x,y). En este caso, el programador es responsable de hacer que cada coordenada se ajuste previamente a la precisión del &#8220;grid&#8221; de referencia. Esto se consigue de manera efectiva invocando al método <em>MakePrecise</em> del correspondiente objeto <em>PrecisionModel</em>. El siguiente ejemplo ilustra una solución para el polígono del ejemplo anterior, dada su lista de coordenadas.</p>
<pre class="brush: cpp; title: ; notranslate">
using System;
using GisSharpBlog.NetTopologySuite.Geometries;
using GisSharpBlog.NetTopologySuite.IO;

namespace Grids
{
 class Program
 {
  public static void Main(string[] args)
  {
   List&lt;double[]&gt; coords = new List&lt;double[]&gt;();
   coords.Add(new double[]{253722.078898558, 4292685.50730101});
   coords.Add(new double[]{253722.07889856, 4292685.50730102});
   coords.Add(new double[]{253735.282733042, 4292698.56531273});
   coords.Add(new double[]{253738.905778592, 4292696.94218745});
   coords.Add(new double[]{253741.840165249, 4292695.62758151});
   coords.Add(new double[]{253735.276565443, 4292675.86615228});
   coords.Add(new double[]{253717.38, 4292636.98});
   coords.Add(new double[]{253700.54, 4292646.23});
   coords.Add(new double[]{253722.078898558, 4292685.50730101});

   PrecisionModel grid = new PrecisionModel(1000); //precisión milimétrica
   GeometryFactory gf = new GeometryFactory(grid); //vinculamos el &quot;grid&quot; con un objeto GeometryFactory
   Geometry g = GetPolygon(coords, gf); //instanciamos el polígono a partir de la lista de coordenadas y el objeto GeometryFactory
   Console.WriteLine(g.ToText()); //mostramos el resultado en WKT
   Console.Write(&quot;Press any key to continue . . . &quot;);
   Console.ReadKey(true);
  }

  static Polygon GetPolygon(List&lt;double[]&gt; coords, GeometryFactory gf)
  {
   List&lt;Coordinate&gt; fixedCoords = new List&lt;Coordinate&gt;();
   foreach (double[] xyPair in coords)
   {
    fixedCoords.Add(GetFixedCoordinate(xyPair[0], xyPair[1], gf));
   }
   return gf.CreatePolygon(gf.CreateLinearRing(fixedCoords.ToArray()), null);
  }

  static Coordinate GetFixedCoordinate(double x, double y, GeometryFactory gf)
  {
   Coordinate c = new Coordinate(x, y);
   gf.PrecisionModel.MakePrecise(ref c); //cálculo de coordenada precisa, es decir, referida al &quot;grid&quot; del modelo de precisión milimétrica que lleva aparejado el objeto GeometryFactory
   return c;
  }
 }
}
</pre>
<p>Si ejecutáis este código deberíais obtener un resultado idéntico al anterior.</p>
<h2>¿Y si no funciona &#8230;?</h2>
<p>Como  dije al principio, podéis usar NTS 1.2 para probar estos ejemplos. Si preferís usar NTS 1.7 seguramente necesitaréis efectuar pequeños cambios en el código. De todos modos, tanto con la versión 1.2 como con la 1.7.1 hay un &#8220;bug&#8221; que hace que, independientemente del factor de escala, el modelo de precisión  fija quede referido siempre a la unidad principal, es decir, a coordenadas enteras. Este &#8220;bug&#8221; está corregido en la versión 1.7.3. Lo bueno de trabajar con software libre es que en la mayoría de casos (y éste es uno de ellos) la solución está a nuestro alcance. Así que, los que trabajéis con la versión 1.2, podéis descargar el código fuente <a title="NTS 1.2 download" href="http://sourceforge.net/projects/nts/files/NetTopologySuite/NetTopologySuite%201.2%20Final%20Release/NetTopologySuite_20feb06.zip/download">aquí</a> y modificar la línea <strong>351</strong> del fichero <strong>PrecisionModel.cs</strong>. Donde dice:</p>
<pre>return Math.Floor(((val * scale) + 0.5d) / scale);</pre>
<p>simplemente debe decir:</p>
<pre>return Math.Floor((val * scale) + 0.5d) / scale;</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.gisandchips.org/2009/11/03/como-definir-y-usar-modelos-de-precision-fija-en-nts/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Gizmo: GIS Meets Objects!</title>
		<link>http://www.gisandchips.org/2009/07/28/gizmo-gis-meets-objects/</link>
		<comments>http://www.gisandchips.org/2009/07/28/gizmo-gis-meets-objects/#comments</comments>
		<pubDate>Tue, 28 Jul 2009 07:33:08 +0000</pubDate>
		<dc:creator>josetomas</dc:creator>
				<category><![CDATA[Programación]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Gizmo]]></category>
		<category><![CDATA[object oriented geodatabases]]></category>
		<category><![CDATA[Perst.NET]]></category>

		<guid isPermaLink="false">http://www.gisandchips.org/wordpress/?p=93</guid>
		<description><![CDATA[De acuerdo, el título parece un tanto pretencioso. Hace años que la tecnología GIS y la programación orientada a objetos confluyeron para hacernos la vida más fácil. Sin embargo, si al empezar vuestro próximo proyecto les decís a vuestros compañeros &#8220;¡eh, esta vez no hay modelo entidad-relación!&#8221; seguro que provoca sorpresa. Efectivamente, alguno de vosotros [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: justify">De acuerdo, el título parece un tanto pretencioso. Hace años que la tecnología GIS y la programación orientada a objetos confluyeron para hacernos la vida más fácil. Sin embargo, si al empezar vuestro próximo proyecto les decís a vuestros compañeros &#8220;¡eh, esta vez no hay modelo entidad-relación!&#8221; seguro que provoca sorpresa. Efectivamente, alguno de vosotros ya se lo imagina: os voy a hablar de bases de datos geográficas orientadas a objetos.<span id="more-93"></span> Las bases de datos orientadas a objetos (OODB) constituyen uno de los campos de investigación y desarrollo más activos en la actualidad, y de hecho son, junto con la interoperabilidad, la clave que determina el futuro de la tecnología GIS. Existen diversas alternativas de OODB de código abierto. Probablemente el proyecto más activo y difundido sea <a title="DB4O" href="http://www.db4o.com/" target="_blank">DB4O</a>. No renuncio a explorar a fondo este gestor más adelante, sobre todo teniendo en cuenta que la comunidad empieza a construir utilidades para el almacenamiento de datos espaciales como <a title="MapMe" href="http://code.google.com/p/mapme/" target="_blank">MapMe</a>. Sin embargo os confieso que en algún punto se cruzó en mi camino <a title="Perst" href="http://www.mcobject.com/perst" target="_blank">Perst</a>, otro OODB bastante popular, y al indagar acerca de los métodos de indexación y comprobar que implementa R-Trees me picó la curiosidad. Muchos de vosotros sabéis que un experimento lleva a otro, y luego a otro, y así sucesivamente hasta que sin darnos cuenta tenemos un API que &#8230; ¡sirve para algo! De modo que aquí os presento <strong>Gizmo</strong>, un API con el que podéis diseñar vuestra propia geodatabase orientada a objetos. Como su propio nombre indica, Gizmo es un trasto, es rudimentario, no hace muchas cosas, pero garantiza un rato de diversión a los curiosos dispuestos a escribir unas pocas líneas en C#.<br />
Para descargar el código fuente de Gizmo podéis hacer un <em>checkout </em>del siguiente repositorio Subversion de GIS&amp;Chips:</p>
<p style="padding-left: 30px"><code>svn co http://www.gisandchips.org/svn/gizmo/release_0.1</code></p>
<p style="text-align: justify">Obtendréis una solución <a title="SharpDevelop" href="http://www.icsharpcode.net/OpenSource/SD/Default.aspx" target="_blank">SharpDevelop</a> en C# con el proyecto del API <strong>Gizmo</strong>. El proyecto compila sobre el framework .NET 2.0 e incluye los ensamblados de referencia de <a title="Perst" href="http://www.mcobject.com/perst" target="_blank">Perst.NET</a>, <a title="NetTopologySuite" href="http://code.google.com/p/nettopologysuite/" target="_blank">NetTopologySuite</a> y <a title="ProjNET" href="http://projnet.codeplex.com/" target="_blank">ProjNET</a>.</p>
<p style="text-align: justify">
<h4>Cómo diseñar nuestro propio modelo</h4>
<p style="text-align: justify">En pocas palabras, Gizmo es un conjunto de interfaces y clases que facilitan al desarrollador la definición de su propio modelo de objetos geográficos de forma que éstos puedan ser almacenados en una base de datos Perst. Como ya he dicho, esto no deja de ser un experimento: no esperéis una herramienta CASE o un interfaz amigable de diseño a base de diagramas. De momento se trata de que podáis definir vuestras clases siguiendo una lógica coherente de derivación e implementación de métodos y propiedades sin preocuparos excesivamente de cómo funciona Perst. Recordad que, al fin y al cabo, el objetivo es que podáis persistir/serializar en un fichero binario cada objeto geográfico que instanciéis, para poder recuperarlo más adelante mediante criterios espaciales gracias a los índices R-Tree. No hay tablas, ni campos, ni registros. No hay <em>Object Relational Mapping</em>. Se trata de vuestros propios objetos, tal y como los hayáis definido en vuestro modelo, con el estado en que se encontraban al serializarlos.<br />
Pero veamos un ejemplo básico de aplicación en línea de comandos con C#. Para nuestro proyecto necesitaremos sendas referencias a Gizmo y Perst.NET. Para mayor comodidad incluid el proyecto de Gizmo en vuestra solución y añadidlo como referencia en vuestro proyecto de aplicación de consola. Respecto a Perst.NET, disponéis de una copia del ensamblado en el directorio <em>lib</em> del proyecto Gizmo (<em>lib\Perst4.NET\PerstNetGenerics.dll) </em>que podéis usar para agregarlo como referencia en vuestro proyecto.</p>
<p style="text-align: justify">Construiremos un modelo muy simple en el que vamos a representar parcelas y conducciones. Esta podría ser la definición de nuestra clase <em>Parcela</em>:</p>
<pre class="brush: csharp; title: ; notranslate">
using System;
using Gizmo.Core;

namespace TestGizmo
{
 public class Parcela : GizmoObject
 {
  string _id = string.Empty;
  string _cultivo = string.Empty;

  public string Id {
   get { return _id; }
  }

  public string Cultivo {
   get { return _cultivo; }
  }

  public Parcela(string wkt, int srid,
                 string id, string cultivo) : base(wkt, srid)
  {
   _id = id;
   _cultivo = cultivo;
  }
 }
}
</pre>
<p>Y esta la definición de la clase <em>Conduccion:</em></p>
<pre class="brush: csharp; title: ; notranslate">
using System;
using Gizmo.Core;

namespace TestGizmo
{
 public class Conduccion : GizmoObject
 {
  string _id = string.Empty;
  string _categoria = string.Empty;

  public string Id {
   get { return _id; }
  }

  public string Categoria {
   get { return _categoria; }
  }

  public Conduccion(string wkt, int srid,
                    string id, string categoria) : base(wkt, srid)
  {
  _id = id;
  _categoria = categoria;
  }
 }
}
</pre>
<p style="text-align: justify">Ya tenemos la primera clave para implementar nuestro modelo con Gizmo: nuestros tipos de entidad geográfica deben derivar de la clase <em>GizmoObject</em> y pasar en el constructor dos parámetros comunes a cualquier objeto geográfico en Gizmo, a saber, la geometría (en nuestro caso en WKT) y el identificador del Sistema de Referencia espacial.</p>
<p style="text-align: justify">El siguiente paso es crear el catálogo de índices de nuestro modelo. Esto nos permitirá implementar búsquedas eficientes por atributos. Para ello definamos una clase <em>Catalogo</em>:</p>
<pre class="brush: csharp; title: ; notranslate">
using System;
using Gizmo.Catalogue;
using Perst;

namespace TestGizmo
{
  public class Catalogo : GizmoCatalogue
  {
    FieldIndex&lt;string, Parcela&gt; _parcelaIdIdx = null;
    FieldIndex&lt;string, Parcela&gt; _parcelaCultIdx = null;
    FieldIndex&lt;string, Conduccion&gt; _conduccionIdIdx = null;
    FieldIndex&lt;string, Conduccion&gt; _conduccionCatIdx = null;

   public FieldIndex&lt;string, Parcela&gt; ParcelaIdIdx {
     get { return _parcelaIdIdx; }
   }

   public FieldIndex&lt;string, Parcela&gt; ParcelaCultIdx {
     get { return _parcelaCultIdx; }
   }

   public FieldIndex&lt;string, Conduccion&gt; ConduccionIdIdx {
     get { return _conduccionIdIdx; }
   }

   public FieldIndex&lt;string, Conduccion&gt; ConduccionCatIdx {
     get { return _conduccionCatIdx; }
   }

   public Catalogo(Storage db, string name) : base(db, name)
   {
     _parcelaIdIdx = db.CreateFieldIndex&lt;string, Parcela&gt;(&quot;Id&quot;, true);
     _parcelaCultIdx = db.CreateFieldIndex&lt;string, Parcela&gt;(&quot;Cultivo&quot;, false);
     _conduccionIdIdx = db.CreateFieldIndex&lt;string, Conduccion&gt;(&quot;Id&quot;, true);
     _conduccionCatIdx = db.CreateFieldIndex&lt;string, Conduccion&gt;(&quot;Categoria&quot;, false);
   }

   public Catalogo()
   {
   }
 }
}
</pre>
<p style="text-align: justify">Como podeis ver aquí la clave está en derivar de la clase <em>GizmoCatalogue</em>, crear en el constructor un índice simple de Perst para cada una de las propiedades de nuestras clases <em>Parcela</em> y <em>Conduccion</em> e implementar los <em>getters </em>correspondientes. Alguien se preguntará &#8220;¿y qué hay del índice espacial?&#8221;: bueno, precisamente de esa tarea se ocupan Gizmo y Perst en última instancia.</p>
<p style="text-align: justify">Finalicemos nuestro modelo agregando una clase <em>layer</em> para cada uno de nuestros tipos de entidad geográfica. Esta es la clase <em>CapaParcelas</em>:</p>
<pre class="brush: csharp; title: ; notranslate">
using System;
using Gizmo.Core;

namespace TestGizmo
{
 public class CapaParcelas : GizmoLayer
 {
  public Catalogo _root = null;

  protected override Type CatalogueType
  {
   get { return typeof(Catalogo); }
  }

  protected override Type TargetType
  {
   get { return typeof(Parcela); }
  }

  public CapaParcelas(GizmoStore gs, string name) : base(gs, name)
  {
   this._root = (Catalogo) base.Root;
  }

  public Parcela FindById(string id)
  {
   return _root.ParcelaIdIdx.Get(id);
  }

  public Parcela[] FindByCultivo(string cultivo)
  {
   return _root.ParcelaCultIdx.Get(cultivo, cultivo);
  }
 }
}
</pre>
<p style="text-align: justify">Y aquí tenéis la clase <em>CapaConducciones</em>:</p>
<pre class="brush: csharp; title: ; notranslate">
using System;
using Gizmo.Core;

namespace TestGizmo
{
 public class CapaConducciones : GizmoLayer
 {
  Catalogo _root = null;

  protected override Type CatalogueType
  {
   get { return typeof(Catalogo); }
  }

  protected override Type TargetType
  {
   get { return typeof(Conduccion); }
  }

  public CapaConducciones(GizmoStore gs, string name) : base(gs, name)
  {
   this._root = (Catalogo) base.Root;
  }

  public Conduccion FindById(string id)
  {
   return _root.ConduccionIdIdx.Get(id);
  }

  public Conduccion[] FindByCategoria(string categoria)
  {
   return _root.ConduccionCatIdx.Get(categoria, categoria);
  }
 }
}
</pre>
<p style="text-align: justify">Las clases <em>layer</em> son el mecanismo para explotar objetos geográficos del mismo tipo. La clave está en designar el tipo asociado mediante la propiedad <em>TargetType</em> e implementar métodos de recuperación de objetos (<em>FindBy&#8230;</em>) mediante los correspondientes índices del catálogo.</p>
<p style="text-align: justify">
<h4 style="text-align: justify">¡Hagamos una prueba!</h4>
<p style="text-align: justify">Ya podemos editar la función <em>Main</em> de nuestra aplicación de consola. No olvidéis añadir el correspondiente  <em>using Gizmo.Core; </em>en la cabecera de vuestro fichero <em>Program.cs</em>. En primer lugar creamos nuestra base de datos geográfica (<em>gizmo.dbs)</em>, la abrimos y creamos las capas de parcelas y conducciones.</p>
<pre class="brush: csharp; title: ; notranslate">
GizmoStore gs = new GizmoStore(&quot;gizmo.dbs&quot;);
gs.Open();
CapaParcelas cp = new CapaParcelas(gs, &quot;parcelas&quot;);
CapaConducciones cc = new CapaConducciones(gs, &quot;conducciones&quot;);
</pre>
<p style="text-align: justify">A continuación almacenamos un  par de parcelas y una conducción a partir de geometrías en WKT en el sistema de proyección UTM 30N ED50 (SRID = 23030).</p>
<pre class="brush: csharp; first-line: 5; title: ; notranslate">
string wktP0 = &quot;POLYGON((10 10, 10 20, 20 20, 20 10, 10 10))&quot;;
string wktP1 = &quot;POLYGON((30 30, 30 40, 40 40, 40 30, 30 30))&quot;;
cp.Add(new Parcela(wktP0, 23030, &quot;P0&quot;, &quot;CEREAL&quot;));
cp.Add(new Parcela(wktP1, 23030, &quot;P1&quot;, &quot;FRUTAL&quot;));
string wktC0 = &quot;LINESTRING(5 5, 45 45)&quot;;
cc.Add(new Conduccion(wktC0, 23030, &quot;C0&quot;, &quot;PRIMARIA&quot;));
</pre>
<p>Como podéis ver,  almacenamos/persistimos nuestras parcelas y conducciones a través de la <em>layer</em> correspondiente invocando el método <em>Add</em>. En la <em>layer</em> <em>CapaParcelas </em>tenemos la parcela <strong>P0</strong> con cultivo de <strong>CEREAL</strong> y la <strong>P1</strong> con cultivo de <strong>FRUTAL</strong>. Y en la <em>layer CapaConducciones </em>tenemos la tubería <strong>C0</strong> de categoría <strong>PRIMARIA</strong>. Hasta aquí todo resulta bastante sencillo. Pero ¿y si queremos saber por qué parcelas pasa la tubería <strong>C0</strong>? Pues bien, esta es la respuesta en 2 líneas:</p>
<pre class="brush: csharp; first-line: 11; title: ; notranslate">

Conduccion c0 = cc.FindById(&quot;C0&quot;);
IGizmoObject[] query = cp.FindCrossedBy(c0);
</pre>
<p>Y si imprimimos las parcelas resultantes,</p>
<pre class="brush: csharp; first-line: 13; title: ; notranslate">

foreach (IGizmoObject p in query)
{
 Console.WriteLine((p as Parcela).Id);
}
gs.Close();
</pre>
<p>el resultado que debemos obtener es <strong>P0</strong> y <strong>P1</strong>.</p>
<p>Por cierto: ¡no olvidéis cerrar vuestra base de datos! (línea de código 17).</p>
<p>Eso es todo. Espero que disfrutéis un rato con Gizmo. En futuros artículos comentaré más aspectos de este proyecto.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gisandchips.org/2009/07/28/gizmo-gis-meets-objects/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Simplify Workbench: banco de pruebas para geometría simplificada</title>
		<link>http://www.gisandchips.org/2009/04/29/simplify-workbench-banco-de-pruebas-para-geometria-simplificada/</link>
		<comments>http://www.gisandchips.org/2009/04/29/simplify-workbench-banco-de-pruebas-para-geometria-simplificada/#comments</comments>
		<pubDate>Wed, 29 Apr 2009 09:40:18 +0000</pubDate>
		<dc:creator>josetomas</dc:creator>
				<category><![CDATA[Programación]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[geometry simplification]]></category>
		<category><![CDATA[PostGIS]]></category>
		<category><![CDATA[SharpMap]]></category>

		<guid isPermaLink="false">http://www.gisandchips.org/wordpress/?p=22</guid>
		<description><![CDATA[Optimizar nuestro cliente GIS, tanto para web como para escritorio, implica entre otras cosas afinar al máximo los rangos de escala de visualización para cada una de nuestras capas. Sin embargo puede darse el caso de que este mecanismo no sea aplicable. Ante esta situación, es importante que no renunciemos al ahorro en los tiempos [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: justify">Optimizar nuestro cliente GIS, tanto para web como para escritorio, implica entre otras cosas afinar al máximo los rangos de escala de visualización para cada una de nuestras capas. Sin embargo puede darse el caso de que este mecanismo no sea aplicable. Ante esta situación, es importante que no renunciemos al ahorro en los tiempos de proceso, y en este sentido puede ser útil recurrir a la simplificación o generalización de la geometría.</p>
<p style="text-align: justify">Lo que aquí os ofrecemos es una herramienta que, dada una determinada entidad PostgreSQL/PostGIS, permite explorar rápidamente cuál es la relación entre tamaño de imagen, extensión del mapa, degradación visual y tiempo de proceso <span id="more-22"></span>para un máximo de 4 valores de tolerancia designados por el usuario. Se trata de una aplicación sencilla, escrita en C#, en la que el área de trabajo se divide en cuatro paneles. En cada panel podemos asignar una tolerancia, visualizar la geometría simplificada resultante, echar un vistazo a la sentencia SQL ejecutada en PostgreSQL y comprobar el tiempo de proceso transcurrido en realizar la transacción en el servidor y generar la imagen resultante en el cliente. Además, en todo momento conocemos cuál es la dimensión en píxeles de la imagen y el ancho del mapa. La instantánea inferior muestra una sesión de Simplify Workbench en la que se compara la geometría simplificada de los municipios de España procedentes de la Base de Datos de Líneas Límite a escala 1:1.000.000 del <a title="IDEE" href="http://www.idee.es" target="_blank">IDEE</a>. Se puede observar cómo el tiempo de proceso del panel C es casi 4 veces menor que el del panel A, mientras que el impacto en la visualización es casi inapreciable. Por el contrario, la reducción del tiempo de proceso en el panel D conlleva una degradación visual que, para la resolución de la imagen, no es aceptable.</p>
<div id="attachment_42" class="wp-caption aligncenter" style="width: 310px"><a href="http://www.gisandchips.org/wp-content/simplifyworkbench3.png"><img class="size-medium wp-image-42" src="http://www.gisandchips.org/wp-content/simplifyworkbench3-300x290.png" alt="Geometría simplificada de los municipios de España en Simplify Workbench. Fuente de datos: IDEE" width="300" height="290" /></a><p class="wp-caption-text">Geometría simplificada de los municipios de España en Simplify Workbench. Fuente de datos: IDEE</p></div>
<p style="text-align: center">
<p style="text-align: justify">Podéis disponer del código fuente de Simplify Workbench bajo licencia GPL haciendo un <em>checkout </em>del siguiente repositorio Subversion de GIS&amp;Chips:</p>
<p style="text-align: justify;padding-left: 30px"><code>svn co http://www.gisandchips.org/svn/simplifyworkbench</code></p>
<p style="text-align: justify">Para los que queráis probarlo de inmediato, tened en cuenta que los datos de conexión a PostgreSQL y entidad PostGIS se configuran en el fichero XML <strong>app.config</strong> que acompaña al ejecutable. Por defecto, os proporcionamos una conexión al servidor PostgreSQL de GIS &amp; Chips para que podáis hacer tests con los municipios del IDEE. Si vais a realizar pruebas con vuestros propios datos tened en cuenta que la función de simplificación de PostGIS (<em>simplify</em> o <em>st_simplify</em>) no opera sobre geometrías en coordenadas geográficas (e.g. SRID = 4326), por lo que la aplicación no realizará el tratamiento esperado. Tampoco opera sobre entidades con sistema de referencia espacial indeterminado en la tabla <em>geometry_columns </em>(i.e. SRID = -1).</p>
<p style="text-align: justify">Si eres desarrollador y te vas a &#8216;zambullir&#8217; en el código seguramente te interesará conocer que Simplify Workbench no es más que una extensión de <a title="SharpMap" href="http://www.codeplex.com/SharpMap" target="_blank">SharpMap</a> con un interfaz gráfico en Windows.Forms. Formalmente es una solución desarrollada en el IDE de código abierto <a title="SharpDevelop" href="http://www.icsharpcode.net/OpenSource/SD/" target="_blank">SharpDevelop</a>, escrita en C# sobre el framework 2.0 de .NET. Integra tres proyectos:  <strong>SharpMap </strong>(versión 0.9), <strong>SharpMapSimplifyExtension</strong> y <strong>Simplify</strong>.</p>
<p style="text-align: justify">Seguramente alguno de vosotros ya conozca <a title="SharpMap" href="http://www.codeplex.com/SharpMap" target="_blank">SharpMap</a>. Desde que <a title="Morten Nielsen" href="http://www.sharpgis.net/" target="_blank">Morten Nielsen</a>, su creador, publicó allá por 2005-2006  este versátil renderizador de cartografía basado en GDI+, una pequeña revolución se ha producido en el ámbito de los clientes GIS libres sobre plataforma .NET y de hecho el proyecto no sólo continúa muy activo sino que ha conseguido integrar un equipo consolidado de desarrolladores en torno a un ecosistema de proyectos interrelacionados. El proyecto <strong>SharpMap</strong> de Simplify Workbench corresponde a los fuentes de la versión 0.9, la versión estable, aunque observaréis que el namespace SharpMap.Data.Providers incluye el proveedor de PostGIS (<a title="PostGIS.cs" href="http://sharpmap.codeplex.com/Wiki/View.aspx?title=PostGIS" target="_blank">PostGIS.cs</a>) y una referencia a <a title="Npgsql" href="http://npgsql.projects.postgresql.org/" target="_blank">Npgsql</a>, la librería de acceso a datos de PostgreSQL en su versión 2.0.4.</p>
<p style="text-align: justify">El proyecto <strong>SharpMapSimplifyExtension</strong> consta de dos clases: <strong>PostGISSimplify</strong> y <strong>VectorSimplifiedLayer</strong>. La primera es una derivación de la clase SharpMap.Data.Providers.PostGIS que modifica el método <em>GetGeometriesInView</em> de forma que la sentencia SQL subyacente invoque la función <em>simplify</em> de PostGIS con la tolerancia de entrada. También añade un delegado que permite recuperar mediante un evento tanto la sentencia SQL final como el ancho del <em>bounding box</em>. La segunda deriva de la clase SharpMap.Layers.VectorLayer, sobrecarga el constructor para instanciar capas vectoriales especificando una tolerancia de simplificación y modifica el método <em>Render</em>. Con estas dos simples extensiones podemos definir  una capa vectorial que muestre geometría simplificada de PostGIS sin variar sustancialmente la lógica habitual de trabajo con SharpMap, tal y como se muestra en este ejemplo:</p>
<pre class="brush: csharp; title: ; notranslate">
//
//
//
string cnstr = &quot;Server=your.pgsql.server;Port=5432;User Id=user;Password=secret;Database=yourDB&quot;;
string tableName = &quot;yourPolygons&quot;;
string geomFieldName = &quot;geometry&quot;;
string oidFieldName = &quot;oid&quot;;
//Instance of data provider for PostGIS simplified geometry
PostGISSimplify src = new PostGISSimplify(cnstr, tableName, geomFieldName, oidFieldName);
//Instance of vector layer to hold simplified PostGIS geometry
//Third parameter is an integer corresponding to tolerance in Douglas-Peucker vertex reduction algorithm
VectorSimplifiedLayer lyr = new VectorSimplifiedLayer(&quot;SIMPLIFIED_LAYER&quot;, src, 1000);
lyr.Style.EnableOutline = true;
lyr.Style.Fill = Brushes.Transparent;
//Map object, the layer container and rendering logic construct in SharpMap
Map m = new Map(new System.Drawing.Size(350, 275));
m.BackColor = Color.White;
m.Layers.Add(lyr);
//Zoom to extents internally invokes the modified GetGeometriesInView method in PostGISSimplify class
m.ZoomToExtents();
//Retrieve map image
System.Drawing.Image img = m.GetMap();
//
</pre>
<p style="text-align: justify">El proyecto <strong>Simplify</strong> incluye el formulario estructurado en cuatro paneles comentado más arriba y una clase de conveniencia, <strong>MapRenderer</strong>, que encapsula la lógica de lectura del fichero app.config, definición de layer, obtención de la imagen, recuperación de la sentencia SQL y cálculo del tiempo de proceso. En el directorio de este proyecto encontraréis también el fichero de solución de SharpDevelop (Simplify.sln).</p>
<p style="text-align: justify">Para terminar, y puesto que muchos de nosotros aquí en GIS &amp; Chips somos fans del framework de código abierto Mono, os animamos a que probéis a compilar usando esta plataforma. Sabemos positivamente que SharpMap 0.9, con pequeñas modificaciones, es compilable sobre Mono. Creemos, aunque no lo hemos comprobado, que el código de Simplify Workbench también lo es. Si alguno lo consigue nos alegrará que nos lo comente.</p>
<p style="text-align: justify">¡Disfrutad con Simplify Workbench!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gisandchips.org/2009/04/29/simplify-workbench-banco-de-pruebas-para-geometria-simplificada/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

