jueves 19 de noviembre de 2009

Etiquetas en los códigos de Digi.Tab

El formato del nuevo Digi.tab que se introdujo en Digi a partir de la versión 2007 es muy flexible ya que está basado en el standard XML, lo que permite añadir campos sin romper la compatibilidad.

En este ejercicio, vamos a modificar un Digi.tab automáticamente para añadirle un concepto que tiene la versión de Digi para MGCP/BTA y que tiene la futura version Digi3D 2010.

En estas versiones de Digi, los códigos pueden tener asociadas etiquetas. Cada código puede tener un número ilimitado de etiquetas, que no son más que nombres separados por comas.

Así pues, si un determinado código tiene el siguiente valor en el campo etiqueta: "Edificios,Edificios en construcción,1:1000" dicho código tendrá tres etiquetas:

a) Edificios
b) Edificios en construcción
c) 1:1000

¿Para que sirven las etiquetas?

Pues básicamente para agrupar códigos.

Si tenemos 10 códigos con la etiqueta "Edificios", podemos ejecutar cualquier orden que trabaje con etiquetas para trabajar con todos esos códigos a la vez.

Ej: En la versión 2010 de DigiNG, podemos ejecutar la orden: BORRA_COD=@Edificios L
para borrar todas las líneas cuyos códigos tengan la etiqueta "Edificios".

Cuando desarrollé el módulo de MGCP para DigiNG, no existía el concepto de etiquetas, sin embargo, era necesario agrupar de alguna manera códigos para hacer topologías. Esto lo hice en el archivo mgcp.xml.

Aquí tenemos un recorte del archivo mgcp.xml



<?xml version="1.0" ?>
<menuMGCP>

<bintop name="Terreno inundable">
BBH090
CBH090
</bintop>

<bintop name="Landcover Area Features">
L1
CZD020
CZD020
CBH030
CBH030
CBH020
CBH130
CBH080
CBH140
CBA040
CDB170
</bintop>

<bintop name="Isla">
BBA030
CBA030
</bintop>

<bintop name="Aeródromo">
BGB005
CGB005
</bintop>

</menuMGCP>


Luego pensé en el concepto de etiquetas para introducir esta tabla de códigos en el digi.tab y añadi la etiqueta "TAGS" a cada uno de los códigos en el digi.tab.

El problema es que luego había que insertar las etiquetas en el digi.tab de MGCP a partir del archivo mgcp.xml de más arriba.

Esta tarea podría haber llevado horas, así que he desarrollado una pequeña aplicación en el lenguaje de programación C# que va a hacer el proceso automáticamente.

Va a cargar el archivo mgcp.xml con los grupos de códigos (en la etiqueta "bintop"), luego va a cargar un digi.tab y va a ir añadiéndo a cada código las etiquetas que le correspondan.

Por último, va a guardar el digi.tab modificado con otro nombre.

Como es una aplicación que se va a ejecutar una única vez, no he dedicado tiempo a comprobar si un determinado código ya tenía una etiqueta o no, eso os lo dejo como tarea de programación.

Utiliza Linq, Xml y XPath.

Aquí está el código:



using System;
using System.Linq;
using System.Xml;
using System.Xml.XPath;

namespace AnadeEtiquetas
{
class Program
{
static void Main(string[] args)
{

XmlDocument digiTab = new XmlDocument();
digiTab.Load("digi.tab.xml");
XPathNavigator navegadorDigiTab = digiTab.CreateNavigator();

XmlReaderSettings settings = new XmlReaderSettings();
settings.ProhibitDtd = false;

using( XmlReader readerMGCP = XmlReader.Create("mgcp.xml", settings) )
{
XPathDocument mgcpXML = new XPathDocument(readerMGCP);

XPathNavigator navegadorMGCP = mgcpXML.CreateNavigator();

foreach(XPathNavigator tablaDeCodigos in navegadorMGCP.Select("/menuMGCP/bintop") )
{
string nombreTabla = tablaDeCodigos.GetAttribute("name", "");

Console.WriteLine("Insertando la tabla {0} en el digi.tab", nombreTabla);

var listaDeCodigos = from código in tablaDeCodigos.Value.Split('\n')
let códigoRecortado = código.Trim()
where códigoRecortado.Length > 0
select códigoRecortado;

foreach (string código in listaDeCodigos)
{
XPathNodeIterator nodosDigiTab = navegadorDigiTab.Select("/digitab/codes/code[@name=\"" + código + "\"]");

foreach (XPathNavigator nodo in nodosDigiTab)
{
if (nodo.MoveToAttribute("tags", ""))
{
nodo.SetValue(nodo.Value + "," + tablaDeCodigos.GetAttribute("name", "").ToString());
}
else
{
nodo.CreateAttribute("", "tags", "", tablaDeCodigos.GetAttribute("name", "").ToString());
}
}
}
}
}

navegadorDigiTab.MoveToRoot();
using (XmlWriter writter = XmlWriter.Create("digitabmodificado.tab.xml"))
{
writter.WriteNode(navegadorDigiTab, false);
writter.Flush();
}
}
}
}

martes 13 de octubre de 2009

MDTop 2010

El otro día, en la feria de usuarios de Esri en España, José Juan vino a ayudarnos.
En uno de esos pocos momentos de relax que tuvimos, me enseñó el prototipo de lo que va a ser MDTop 2010.

Si MDTop ya era fácil de manejar, ni te imaginas lo intuitivo que va a ser MDTop 2010.

El interface de usuario ha cambiado completamente. Ahora está basado en el famoso interface Ribbon que Microsoft incorporó en Office 2007.



La barra ribbon está llena de botones con unos iconos increiblemente bien dibujados y además disponen de texto para saber el significado de cada botón.

El resultado: Ha desaparecido el menú del programa, ya que el menú ahora es una super barra de herramientas.

Pero eso no es todo: Ahora MDTop 2010 utiliza como motor de presentación OpenGL, lo que significa que los vectores se dibujan rapidísimo. Esto es porque MDTop 2010 está preparado para trabajar con conjuntos enormes de datos de Lidar. Con una tarjeta gráfica adecuada, se pueden dibujar millones de vértices por grame.

Además VirtuaLand desaparece como programa independiente y se incorpora en MDTop 2010, así tenemos un único programa que hará las dos cosas (si se dispone de la licencia correspondiente para hacer las dos cosas).

De Ferias con Esri España

Tengo que agradecer muchoa Esri España, y personalmente a Alfonso Rubio por darnos la oportunidad de presentar la extensión "Digi3D para ArcGIS" en la Conferencia de Usuarios ESRI España 2009.



Allí dispusimos de un stand para presentar el producto (que aún está en desarrollo, pero suficientemente avanzado para poderlo mostrar al público en general).

Lo que mostramos en esa feria era tan nuevo, que hasta el driver de las gafas estereoscópicas que llevamos, lo habían publicado los de nVidia ¡un día antes!.

Menuda semana para preparar el programa, documentación, las diapositivas de la ponencia (gracias José Juan), la tarjeta gráfica, el monitor estereoscópico...



Además dispusimos de media hora para dar una charla en una sala de conferencias y explicar las características de Digi3D para ArcGIS.

En uno de los pocos momentos de relax pude asistir a una conferencia super interesante de José M. García y Pablo Ramos sobre migración de una aplicación de escritorio con ArcObjects en .NET al servidor de ArcGis, y no pude asistir a dos conferencias super interesantes de SilverLight, pero otra vez será. No se puede estar en dos sitios a la vez.

Todo funcionó como un Reloj, cómo se nota que Esri es un gigante.

La feria terminó un viernes y el domingo estábamos volando Manuel y yo a Bogotá.



Hemos conocido gente de Chile, Argentina, Bolivia, Perú, Ecuador, Colombia, Venezuela, Costa Rica, Puerto Rico, México, USA, ... y hemos hecho muchos amigos. Ni os imaginais lo divertidos que son los Mexicanos.

El lunes conocimos al staff de Procálculo Prosis (Esri Colombia), y realizamos una serie de pruebas para montar dos monitores estereoscópicos (el de ellos y el que llevaba Manuel en su maleta).

Al final resulta que nuestra tarjeta nVidia Quadro FX 3800, no puede (creo que por el momento) sacar una frecuencia de 120Hz por sus conectores DisplayPort, así que probamos con la tarjeta que tenían en uno de sus ordenadores, una Quadro FX 3700, con dos conectores DVI y pudimos trabajar en estéreo con los dos monitores.

La feria comenzó el miércoles, pero el martes presentamos Digi3D para ArcGIS y todas nuestras aplicaciones a los distribuidores.

El resto de días, no salimos de nuestro stand porque no nos dejaban, teníamos visitas constantemente.

Funcionó todo, hicimos cosas arriesgadas en una demostración "en vivo", como correlar un modelo de objeto cercano (la famosa Capilla que muchos conocéis) y salió perfecto. Cámbios automáticos de modelos, orientaciones, vamos, que en esa semana enseñamos todas nuestras armas y todo funcionó a la perfeccion.

lunes 14 de septiembre de 2009

Actualización de E-Mail File Attachment Using MIME (with HTML support)

Lo prometido es deuda y, después de una semana, han publicado mi artículo en la siguiente dirección: http://www.codeguru.com/cpp/i-n/internet/email/article.php/c16401/

Es una lástima, pero todo lo que puse en el post anterior no me sirvió de nada, porque resulta que no me di cuenta de que Digi3D está encriptado, por lo que las capturas de MiniDump no me sirven de nada, pero al menos me divertí mucho programándolo.

viernes 28 de agosto de 2009

Detección de excepciones no controladas



Ayer estuve en una empresa y ocurrió algo que suele pasar de vez en cuando:

Un operador me dijo que Digi3D a veces se le salía inesperadamente al ejecutar una determinada orden. El único inconveniente es que no sabía provocar el fallo, lo que hace que sea muy complicado reproducirlo y por lo tanto corregirlo.

En este tipo de situaciones, la única posibilidad que tenía es irme a la empresa en cuestión con un ordenador portatil, instalarle un módulo denominado Visual Studio Remote Debugger, copiar una versión especial de Digi3D, ejecutarla y cruzar los dedos esperando que el usuario consiga reproducir el error.

La mayoría de las veces, únicamente necesito que se reproduzca el error una vez. En cuanto veo lo que está pasando, tardo un milisegundo en saber cómo arreglarlo. Otras veces, es más complicado y necesito reproducirlo una y otra vez.

Pero el principal inconveniente es que tiene que suceder cuando estoy allí utilizando el depurador remoto. Si no, no me sirve de nada que me digan "Se me sale de vez en cuando".

En este caso particular, el problema es que el operador pulsaba Esc mientras se estaba destruyendo un cuadro de diálogo y Digi capturaba la pulsación de tecla destruyendo la orden que había desencadenado que saliera dicho cuadro de diálogo. Yo no he sido capaz de reproducirlo, y ayer estuve toda la tarde intentándolo. Saqué ese cuadro de diálogo unas mil veces, y nada de nada.

Así que me puse a pensar cómo puedo hacer para poder copiar el estado de la máquina del cliente justo cuando se detecta que el programa va a finalizar inesperadamente, y me acordé de un programa que venía en Windows XP denominado Dr. Watson.

Este programa genera un archivo "mini-dump" que es justo lo que necesito para poder reproducir el problema en el ordenador que tengo en la oficina de Digi21.

Buscando por la web, dí con un vídeo de Microsoft que demuestra cómo utilizar este archivo de Mini Dump.

El único inconveniente es que era necesario que el usuario active Dr. Watson, así como que el usuario copiara manualmente los archivos de dump generador y me los enviara por correo electrónico, sueño que sé que nunca se iba a cumplir.

Así que tenía dos problemas:

a) No quiero que el usuario tenga que activar Dr. Watson manualmente.
b) Quiero que los archivos de MiniDump se envíen automáticamente a mi servidor para poder corregir el error que ha desencadenado la generación de dicho archivo MiniDump tan pronto como sea posible.

Para solucionar el primer problema, busqué APIs en MSDN hasta que dí con la API que utiliza DrWatson, que se llama MiniDumpWriteDump. Esta API me permite generar el archivo sin necesidad de activar DrWatson ni nada de nada.

Digi3D utiliza las librerías MFC. La aplicación está representada por una clase que hereda de la clase CWinApp.

CWinApp a su vez, tiene un método virtual denominado CWinApp::Run que es el método que ejecuta todo el programa. Así que únicamente tenía que sobrecargar dicho método y llamar al de la clase base dentro de un try catch:

__try {
__super::Run();
}
__catch(...)
{
// Procesar la excepción
}


En este punto lo único que tengo que hacer es generar un archivo de MiniDump de forma automática además de mostrarle al usuario un cuadro de diálogo indicándole que se ha producido un error inesperado, que se ha generado el archivo de MiniDump para ayudar al desarrollador a localizar y corregir el error y que se va a proceder a enviar dicho archivo al servidor de Digi21.

Aquí comienza el segundo problema: ¿Cómo envío el archivo al servidor?

La primera solución que se me ocurrió fué crear un servicio en nuestro servidor, tal y como ya tenemos servicios para generar licencias de Júpiter para proyectos del estilo Grafcan o Geomadrid, pero no estaba muy convencido porque no me apetece cargar el servidor con servicios cada vez que se me ocurra algo nuevo.

La siguiente idea que me surgió fué enviarla por correo electrónico. Si el programa que ha fallado es Digi3D, que se me envíe un email a mí, y si ha sido por ejemplo MDTop, que se le envíe a José Juan.

Pero aquí surge otro problema: Puede que el usuario no tenga programa cliente de correo electrónico. Yo por ejemplo utilizo a diario tres ordenadores, y sólo tengo Outlook en uno de ellos. Con los otros dos utilizo correo web.

Así que no me quedaba más remedio que ponerme a programar una clase que me permita enviar correo sin necesidad de tener un programa cliente de correo electrónico.

Los correos electrónicos se envían mediante el protocolo SMTP (Simple Mail Transfer Protocol), que como su nombre indica, es muy simple.

Sin embargo la cosa se complica un poco cuando queremos enviar un archivo adjunto ya que además hay que utilizar Mime (Multipurpose Internet Mail Extensions) y eso ya es un poco más complicado, que no imposible, así que decidí buscar a ver si alguien ya lo había hecho.

Existen dos fuentes de código en internet que solemos utilizar los programadores: TheCodeProject y CodeGuru.

En estos sitios puedes buscar un componente y, si tienes suerte, alguien ya lo ha programado antes y lo ha subido a cualquiera de los dos servicios.

Hay dos tipos de personas en el mundo: Creativas y No creativas. Las no creativas únicamente utilizan estos servicios y nunca aportan nada a la comunidad. No envían mensajes de feedback, no corrigen errores y por supuesto, nunca comparten nada.

Las creativas sin embargo actúan al revés.

Yo me considero creativo, y tengo varios artículos en TheCodeProject y hoy voy a subir un artículo a CodeGuru.

Resulta que he localizado en CodeGuru un proyecto que es exáctamente lo que quiero:
E-Mail File Attachment Using MIME (with HTML support), programado por Wes Clyburn.

Lo descargo, lo modifico para que funcione con el servidor de correo de Digi21.net y descubro que no funciona, no porque esté mal programado, sino porque el servidor de correo Digi21 requiere autenticación, y éste componente no está programado con autenticación.

Así que me he puesto a estudiar el protocolo SMTP Service Extension for Authentication y he modificado el código fuente del proyecto que he descargado de CodeGuru.

Ahora este componente admite autenticación, así que esta misma tarde voy a enviar el proyecto a CodeGuru modificado para que cualquiera pueda utilizarlo.

Lo siguiente ha sido añadir este componente a Digi3D y ya tengo el servicio de envío de archivos MiniDump automatizados a mi cuenta de correo electrónico.

Por ahora lo he hecho en la verión Digi3D 2007 MGCP y la semana que viene lo haré para la versión 2007 y 2010 Beta.

Así que a partir de ahora es como si estuviera depurando remotamente en todos los equipos con Digi3D que se ejecutan en el mundo.

Si se produce algún error, me llegará dicho informe y podré corregirlo. Así de simple.

martes 25 de agosto de 2009

Vectores estereoscópicos en Digi3D para ArcMAP

El complemento de Digi3D para ArcMAP sigue creciendo poco a poco.

Ya he conseguido mostrar vectores estereoscópicos con los colores y grosores que utiliza ArcMAP para dibujar en la ventana de mapa.



Por ahora dibujo capas de tipo polilínea y capas compuestas, como la de la captura de pantalla que es una capa CAD.

El único inconveniente por ahora es que la combinación ArcMAP/Digi3D no es tán rápida como la combinación DigiNG/Digi3D, por lo que un modelo que Digi3D regenera en décimas de segundo, pasa a ser casi un segundo en Arc/Digi3D.

No estoy utilizando aceleración gráfica en todo el proceso. Mañana añadiré más aceleración gráfica y espero acercarme a la velocidad de DigiNG.

domingo 23 de agosto de 2009

Programación de DigiNG con .NET

La versión 2010 de Digi3D se retrasa día tras día, porque el trabajo se me acumula y nunca encuentro tiempo para finalizarla.

Una de las principales ventajas que va a tener esta versión es que está programada en C++/CLI lo que significa que es una aplicación .NET y va a permitir a los usuarios que tengan conocimientos de programación crear complementos que se ejecuten dentro de la aplicación como si estuvieran desarrollados por Digi21 y así adaptar el programa al modelo de negocio propio.

La versión 2007 de Digi3D está programada con Visual Studio 2003, y con este compilador no se puede programar en C++/CLI. La única manera de mezclar C++ y .NET con este compilador es utilizar una extensión horrible que se inventaron los amigos de Microsoft denominada "Extensiones Administradas de C++". Yo me compré un libro para aprender a programar en este lenguaje, y aprendí, pero cuando también sabes programar en C++/CLI se te quitan las ganas de programar con C++ y sus extensiones administradas, así que la versión 2007 se queda sin programación en .NET.

¿o no?

Pero antes de seguir ¿Por qué quiero que DigiNG sea programable en .NET?

Sencillamente porque si la aplicación es capaz de comunicarse con cualquier complemento programado en .NET, se podrá comunicar con complementos programados en C#, en Visual Basic, en J#, F#, Perl, ... cualquier lenguaje de programación que conozcamos y que pueda compilarse contra .NET.

Continuando con Digi3D 2007, ¿cómo conseguir que se pueda comunicar con complementos programados en .NET aunque sea con peor rendimiento que el que podremos disfrutar con la versión Digi3D 2010?

La respuesta se llama COM (Component Object Model).

Resulta que con Visual Studio 2003 puedo programar sin ninguna dificultad aplicaciones COM, y con cualquier compilador de .NET puedo programar componentes COM, así que asunto solucionado.

He dedicado un tiempo a hacer que la versión Digi3D 2007 MGCP se pueda programar mediante COM, y todos los ejemplos de programación los estoy haciendo en C# y en VBasic.

Lo bueno de todo es que no tenemos que invertir ni un euro en compiladores, ya que Microsof dispone de compiladores gratuitos denominados Visual Studio 2008 Express Editions. Para aquellos que os guste el software libre, teneis otro compilador denominado SharpDevelop

Estoy intentando que el modelo de objetos COM sea lo más parecido posible a cómo será el modelo de objetos .NET que tendremos en la versión 2010 de Digi3D. De esta manera, cuando tengamos Digi3D 2010, podremos recompilar nuestros complementos para incrementar su velocidad de ejecución.




Vamos a ver cómo podríamos simular la orden BININFO de DigiNG en C#:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using DigiNG.Interop;
using DigiNG.Interop.Entities;
using DigiNG.Interop.Math;

namespace BuscarAfluentesConAnchoMayorQueRio
{
[ClassInterface(ClassInterfaceType.None)]
[Guid("D31D7D9E-F3B1-4edd-8E36-54F842E8EC46")]
public class BinInfo : ICommand
{
#region Miembros de ICommand

public bool Run(IDigiNGServer server)
{
server.Clear();

List entidadesABorrar = new List();
List entidadesAAñadir = new List();

foreach (IEntity entidad in server.GetEntities(new string[] { "*" }))
{
server.AddString("Entidad");
foreach(ICode code in entidad.Codes)
server.AddString(string.Format("{0} con tabla: {1} y con ID: {2}", code.Name, code.Table, code.ID));

if (entidad.Deleted)
server.AddString("La entidad está borrada");
else
server.AddString("La entidad no está borrada");


server.AddString(string.Format(" {0},{1},{2}", entidad.MaxMin.XMax, entidad.MaxMin.YMax, entidad.MaxMin.ZMax));
server.AddString("+-----------------------+");
server.AddString("| |");
server.AddString("+-----------------------+");
server.AddString(string.Format("{0},{1},{2}", entidad.MaxMin.XMin, entidad.MaxMin.YMin, entidad.MaxMin.ZMin));


if (entidad is ILineEntity)
{
ILineEntity línea = (ILineEntity)entidad;

server.AddString("La entidad es una línea.");

if( línea.Closed )
server.AddString("La línea está cerrada");

foreach (IPoint3DD coordenada in línea.Vertex)
server.AddString(string.Format("{0}, {1}, {2}", coordenada.X, coordenada.Y, coordenada.Z));
}
else if (entidad is IPointEntity)
{
server.AddString("La entidad es un punto");

IPointEntity punto = (IPointEntity)entidad;

server.AddString(string.Format("Rotación del punto: {0} radianes", punto.Rotation));
server.AddString(string.Format("Coordenadas de inserción del punto: {0}, {1}, {2}", punto.Coordinate.X, punto.Coordinate.Y, punto.Coordinate.Z));
}
else if (entidad is ITextEntity)
{
server.AddString("La entidad es un texto");

ITextEntity texto = (ITextEntity)entidad;
server.AddString(string.Format("Rotación del texto: {0} radianes", texto.Rotation));
server.AddString(string.Format("Altura del texto: {0} radianes", texto.Height));
server.AddString(string.Format("Justificación: {0}", texto.Justification.ToString()));
server.AddString(string.Format("Cadena del texto: {0}", texto.Text));
}

server.AddString("-------------------------------------------------------");
}

return true;
}

#endregion
}
}