Gizmo: GIS Meets Objects!
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 “¡eh, esta vez no hay modelo entidad-relación!” 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. 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 DB4O. 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 MapMe. Sin embargo os confieso que en algún punto se cruzó en mi camino Perst, 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 … ¡sirve para algo! De modo que aquí os presento Gizmo, 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#.
Para descargar el código fuente de Gizmo podéis hacer un checkout del siguiente repositorio Subversion de GIS&Chips:
svn co http://www.gisandchips.org/svn/gizmo/release_0.1
Obtendréis una solución SharpDevelop en C# con el proyecto del API Gizmo. El proyecto compila sobre el framework .NET 2.0 e incluye los ensamblados de referencia de Perst.NET, NetTopologySuite y ProjNET.
Cómo diseñar nuestro propio modelo
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 Object Relational Mapping. 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.
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 lib del proyecto Gizmo (lib\Perst4.NET\PerstNetGenerics.dll) que podéis usar para agregarlo como referencia en vuestro proyecto.
Construiremos un modelo muy simple en el que vamos a representar parcelas y conducciones. Esta podría ser la definición de nuestra clase Parcela:
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;
}
}
}
Y esta la definición de la clase Conduccion:
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;
}
}
}
Ya tenemos la primera clave para implementar nuestro modelo con Gizmo: nuestros tipos de entidad geográfica deben derivar de la clase GizmoObject 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.
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 Catalogo:
using System;
using Gizmo.Catalogue;
using Perst;
namespace TestGizmo
{
public class Catalogo : GizmoCatalogue
{
FieldIndex<string, Parcela> _parcelaIdIdx = null;
FieldIndex<string, Parcela> _parcelaCultIdx = null;
FieldIndex<string, Conduccion> _conduccionIdIdx = null;
FieldIndex<string, Conduccion> _conduccionCatIdx = null;
public FieldIndex<string, Parcela> ParcelaIdIdx {
get { return _parcelaIdIdx; }
}
public FieldIndex<string, Parcela> ParcelaCultIdx {
get { return _parcelaCultIdx; }
}
public FieldIndex<string, Conduccion> ConduccionIdIdx {
get { return _conduccionIdIdx; }
}
public FieldIndex<string, Conduccion> ConduccionCatIdx {
get { return _conduccionCatIdx; }
}
public Catalogo(Storage db, string name) : base(db, name)
{
_parcelaIdIdx = db.CreateFieldIndex<string, Parcela>("Id", true);
_parcelaCultIdx = db.CreateFieldIndex<string, Parcela>("Cultivo", false);
_conduccionIdIdx = db.CreateFieldIndex<string, Conduccion>("Id", true);
_conduccionCatIdx = db.CreateFieldIndex<string, Conduccion>("Categoria", false);
}
public Catalogo()
{
}
}
}
Como podeis ver aquí la clave está en derivar de la clase GizmoCatalogue, crear en el constructor un índice simple de Perst para cada una de las propiedades de nuestras clases Parcela y Conduccion e implementar los getters correspondientes. Alguien se preguntará “¿y qué hay del índice espacial?”: bueno, precisamente de esa tarea se ocupan Gizmo y Perst en última instancia.
Finalicemos nuestro modelo agregando una clase layer para cada uno de nuestros tipos de entidad geográfica. Esta es la clase CapaParcelas:
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);
}
}
}
Y aquí tenéis la clase CapaConducciones:
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);
}
}
}
Las clases layer son el mecanismo para explotar objetos geográficos del mismo tipo. La clave está en designar el tipo asociado mediante la propiedad TargetType e implementar métodos de recuperación de objetos (FindBy…) mediante los correspondientes índices del catálogo.
¡Hagamos una prueba!
Ya podemos editar la función Main de nuestra aplicación de consola. No olvidéis añadir el correspondiente using Gizmo.Core; en la cabecera de vuestro fichero Program.cs. En primer lugar creamos nuestra base de datos geográfica (gizmo.dbs), la abrimos y creamos las capas de parcelas y conducciones.
GizmoStore gs = new GizmoStore("gizmo.dbs");
gs.Open();
CapaParcelas cp = new CapaParcelas(gs, "parcelas");
CapaConducciones cc = new CapaConducciones(gs, "conducciones");
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).
string wktP0 = "POLYGON((10 10, 10 20, 20 20, 20 10, 10 10))"; string wktP1 = "POLYGON((30 30, 30 40, 40 40, 40 30, 30 30))"; cp.Add(new Parcela(wktP0, 23030, "P0", "CEREAL")); cp.Add(new Parcela(wktP1, 23030, "P1", "FRUTAL")); string wktC0 = "LINESTRING(5 5, 45 45)"; cc.Add(new Conduccion(wktC0, 23030, "C0", "PRIMARIA"));
Como podéis ver, almacenamos/persistimos nuestras parcelas y conducciones a través de la layer correspondiente invocando el método Add. En la layer CapaParcelas tenemos la parcela P0 con cultivo de CEREAL y la P1 con cultivo de FRUTAL. Y en la layer CapaConducciones tenemos la tubería C0 de categoría PRIMARIA. Hasta aquí todo resulta bastante sencillo. Pero ¿y si queremos saber por qué parcelas pasa la tubería C0? Pues bien, esta es la respuesta en 2 líneas:
Conduccion c0 = cc.FindById("C0");
IGizmoObject[] query = cp.FindCrossedBy(c0);
Y si imprimimos las parcelas resultantes,
foreach (IGizmoObject p in query)
{
Console.WriteLine((p as Parcela).Id);
}
gs.Close();
el resultado que debemos obtener es P0 y P1.
Por cierto: ¡no olvidéis cerrar vuestra base de datos! (línea de código 17).
Eso es todo. Espero que disfrutéis un rato con Gizmo. En futuros artículos comentaré más aspectos de este proyecto.


Hola amigos, muy buen trabajo, me surge una duda, el código incluye algunas partes que creo que no han sido correctamente codificadas apareciendo partes con "e, etc. ¿Tiene su correspondencia con el signo “” de cadenas de tipo String?
Gracias de antemano.
Saludos.
Fran
Fran,
He revisado y corregido los bloques de código. Creo que ahora los verás correctamente, sin secuencias html. Gracias por indicarme este problema.