Cuando arrancamos una aplicación Android, el sistema operativo crea un nuevo ‘hilo’ de procesamiento para la aplicación. En ese hilo, llamado hilo principal, es donde se ejecutan todas las operaciones del programa, incluidas las tareas relacionadas con la actualización del interfaz de usuario. Es por ello, que cuando realizamos una operación que consuma un tiempo apreciable, dejamos momentaneámente congelado el interfaz de usuario. Durante ese tiempo el interfaz no responderá a las acciones como pulsar botones, actualizaciones de pantalla, etc. En suma, es como si se quedara temporalmente colgada la aplicación. Ejemplos de este tipo de operaciones pueden ser el acceso a ficheros de la tarjeta de memoria, las consultas a través de internet o la inicialización de objetos complejos. Con el fin de que el interfaz de la aplicación atienda adecuadamente en todo momento a los eventos generados en el interfaz de usuario, es conveniente que dichas operaciones se hagan en otro hilo de procesamiento diferente del hilo principal

Android dispone de la clase AsyncTask pensada para dar solución a numerosas situaciones como las planteadas. La clase AsyncTask tiene un funcionamiento muy similar al que ofrece la clase SwingWorker cuando se trabaja en entornos Java-Swing

Para utilizarla tendremos que derivar una clase que descienda de AsyncTask y sobreescribir, al menos, el método doInBackground(). Todo lo que ejecutemos dentro de ese método se ejecutará en un hilo propio creado por AsyncTask, diferente del hilo que instanció nuestra clase, que generalmente será el hilo principal. AsyncTask ofrece otros métodos que nos permitirán comunicarnos con el hilo principal y transmitirle el progreso del proceso o el resultado final del mismo. Se trata de los métodos onProgressUpdate() y onPostExecute(). Si sobreescribimos estos métodos, todo lo que ejecutemos en ellos se ejecutará en el hilo principal. El método onProgressUpdate() se ejecutará cada vez que llamemos al método publishProgress() explicitamente desde dentro del método doInBackground(). El método onPostExecute() es llamado automáticamente cuando finaliza el proceso de doInBackground().

Hay tres clases genéricas que debemos indicar al derivar nuestra clase de AsyncTask: La primera corresponde al tipo de los parametros que recibe el método doInBackground(). La segunda corresponde a los parámetros que recibe el método onProgressUpdate(). La tercera clase es la de los valores devueltos por el método doInBackground() que coincide con los parámetros que se pasan al método onPostExecute()..

Veamos un ejemplo de un worker que actúa de contador. Dentro del método doInBackground() se va incrementando un contador cada segundo. Cada vez que incrementa el contador, manda publicar el resultado en un TextView existente en el UI.

public class MainActivity extends Activity {

   TextView textView;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      textView = (TextView)findViewById(R.id.textView);

      Counter counter = new Counter();
      counter.execute(10);
   }

   class Counter extends AsyncTask<Integer, Integer, Integer> {
      @Override
      protected Integer doInBackground(Integer... maxcount) {
         int current = 0;
         while (current < (int)maxcount[0]) {
            try {
               Thread.sleep(1000);
               current++;
               Log.d("TEST",
                     String.format("Contador: %d", current));
               publishProgress(current);
            } catch(Exception e) {
               Log.d("TEST", "Error en doInBackground()");
            }
         }
         return null;
      }
      @Override
      protected void onProgressUpdate(Integer... values) {
         Log.d("TEST", "Counter.onProgressUpdate()");
         for(Integer value: values) {
            textView.setText(value.toString());
         }
      }
   }
}

Hay que destacar que el método 'onProgressUpdate() recibe un array de parámetros del tipo declarado. Puede suceder que enviemos un mensaje a publicar y no sea publicado inmediatamente. En cualquier caso, cuando se ejecute el método, dara salida a los mensajes pendientes.

Vemos la forma de invocar la tarea desde la actividad principal: primero se instancia un objeto de la clase y luego se llama a su método 'execute()'. Podemos forzar que nuestra Activity espere el resultado de la tarea asíncrona llamando al método get() de la siguiente manera:

Counter counter = new Counter();
counter.execute(10);
try {
   int result = counter.get();
} catch (Exception e) {
   Log.w("TEST","Error in get");
}

Esto forzará al hilo principal a esperar un resultado antes de seguir.

Por último, indicar que esta clase está pensada para tareas de corta duración, pongamos unos pocos segundos. Para tareas que lleven más tiempo, Android dispone de otros mecanismos específicos como los Service. También es posible utilizar el mecanismo de Threads y ThreadPools de Java. Encontraréis más documentación en Processes and Threads



Santiago Higuera (28 de febrero de 2014)

[gview file="http://mercatorlab.com/downloads/DiferenciaAltitudGPSMDE.pdf"]



Santiago Higuera (21 de febrero de 2014)

Navegación a por estima

Vamos explicar el método que se utiliza en náutica para calcular la distancia y el rumbo directo entre dos puntos de los que se conocen sus coordenadas geográficas. El procedimiento proporciona valores precisos para puntos cuya separación sea menor de unas doscientas millas, esto es, unos 370 kilómetros. Es el denominado problema de estima inversa en navegación loxodrómica.

No vamos a entrar en las matemáticas del asunto, sino que vamos a utilizar una regla nemotécnica para memorizar dos triángulos rectángulos que nos permitirán deducir las fórmulas correspondientes. La justificación matemática se puede encontrar en cualquier tratado de náutica. A mí me gusta más que otros el libro de Luis Mederos ‘Navegación Astronómica’.

El problema que queremos resolver es, conocidas las coordenadas geográficas de dos puntos, calcular la distancia entre ellos y el rumbo directo a seguir para ir del primer al segundo punto. Llamaremos longitud1, latitud1, longitud2 y latitud2 a las coordenadas conocidas de los puntos.

Los dos triángulos rectángulos que nos proporcionan las fórmulas para relacionar las variables son los de la figura, donde el significado de las variables es el siguiente:

  • Δl: Incremento de latitud, esto es, latitud2-latitud1
  • R: rumbo para ir del primer punto al segundo. El rumbo es un ángulo medido desde el Norte hacia el Este (a derechas)
  • D: distancia entre los dos puntos
  • Ap: Apartamiento, esto es, la longitud del arco comprendido entre dos meridianos, medido a una latitud concreta. (ver ref)
  • lm: Latitud media, o sea, (latitud1+latitud2)/2
  • ΔL: incremento de longitud, es decir, (longitud2 – longitud1)

Calcularemos en primer lugar el Apartamiento, Ap, a partir de la fórmula que deducimos del segundo triángulo:

Ap = ΔL * cos(lm)

En esta fórmula, como en las demás, es muy importante la utilización de las unidades correctas. En general, las coordenadas de los puntos inicial y final vendrán expresadas en grados sexagesimales. Tenemos que pasar todos los ángulos a radianes antes de comenzar a operar.

Una vez calculado el Apartamiento utilizaremos el primer triángulo de la figura para calcular el Rumbo y la Distancia:

R = atan( Ap / Δl)
D = asin( Ap / sin(R) )

El rumbo obtenido estará expresado en radianes, por lo que habrá que pasarlo a grados para que sea de utilidad en el compás

La distancia también está en radianes. Habra que pasarla a minutos de arco, o lo que es lo mismo, a millas. Una vez en millas podremos expresarla en metros multiplicando por 1851:

D (millas) = D (minutos) = D (rad) * 180.0 / π * 60
D (metros) = D (millas) * 1851

Os dejo un enlace a las rutinas java que resuelven el problema:

loxodromia.java

Por último indicar que la regla nemotécnica para memorizar los triángulos es la frase que aparece bajo la figura: Alrededor del Aparcamiento apareció la madre de Luis



Santiago Higuera (4 de febrero de 2014)

Feb 022014

El formato GPX, sin ser un estándar reconocido, ha sido un estándar de facto para la recogida y transmisión de datos con dispositivos GPS. El formato GPX fue desarrollado por Topographix.

GPX es una particularización del lenguaje XML adaptada para recoger información procedente de GPS. Un documento GPX es una colección de WayPoints, Routes y Tracks. Un documento GPX concreto no tiene por qué tener todos los elementos, puede tratarse simplemente de un documento con uno o varios Tracks, o quizás solo un Route, o unos cuantos WayPoints.

Tanto los Routes como los Tracks son colecciones de WayPoints, por lo que vamos a explicar primero qué es un WayPoint. Un WayPoint almacena los datos de una posición tomada con un dispositivo GPS: la Longitud y la Latitud. El WayPoint puede guardar también la Altitud del punto, la fecha-hora de la medida y otra serie de datos relacionados con la toma de posición por parte del GPS (precisión, fix,…). El formato prevé la posibilidad de dotar al WayPoint de un nombre y una descripción. El esquema de un WayPoint, según la especificación de versión 1.1, es el siguiente:

<xsd:complexType name="wptType">
   <xsd:sequence>
   <-- elements must appear in this order -->
   <-- Position info -->
      <xsd:element name="ele" type="xsd:decimal" minOccurs="0"/>
      <xsd:element name="time" type="xsd:dateTime" minOccurs="0"/>
      <xsd:element name="magvar" type="degreesType" minOccurs="0"/>
      <xsd:element name="geoidheight" type="xsd:decimal" minOccurs="0"/>
      <-- Description info -->
      <xsd:element name="name" type="xsd:string" minOccurs="0"/>
      <xsd:element name="cmt" type="xsd:string" minOccurs="0"/>
      <xsd:element name="desc" type="xsd:string" minOccurs="0"/>
      <xsd:element name="src" type="xsd:string" minOccurs="0"/>
      <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded"/>
      <xsd:element name="sym" type="xsd:string" minOccurs="0"/>
      <xsd:element name="type" type="xsd:string" minOccurs="0"/>
      <-- Accuracy info -->
      <xsd:element name="fix" type="fixType" minOccurs="0"/>
      <xsd:element name="sat" type="xsd:nonNegativeInteger" minOccurs="0"/>
      <xsd:element name="hdop" type="xsd:decimal" minOccurs="0"/>
      <xsd:element name="vdop" type="xsd:decimal" minOccurs="0"/>
      <xsd:element name="pdop" type="xsd:decimal" minOccurs="0"/>
      <xsd:element name="ageofdgpsdata" type="xsd:decimal" minOccurs="0"/>
      <xsd:element name="dgpsid" type="dgpsStationType" minOccurs="0"/>
      <-- Extensions -->
      <xsd:element name="extensions" type="extensionsType" minOccurs="0"/>
   </xsd:sequence>
   <xsd:attribute name="lat" type="latitudeType" use="required"/>
   <xsd:attribute name="lon" type="longitudeType" use="required"/>
</xsd:complexType>

Una característica interesante del esquema del WayPoint es que admite lo que denomina extensiones. Cada WayPoint puede llevar información adicional en forma de un elemento extensions a definir por el productor de la información. De esta manera podemos incorporar a cada WayPoint la información procedente de determinados sensores, por ejemplo, o cualquier otra que nos interese almacenar junto con las coordenadas del punto (Todos los elementos GPX admiten la posibilidad de tener extensions, también los Routes y los Tracks o el propio documento GPX). Un ejemplo de WayPoint, que incorpora información adicional en su elemento extensions, podría ser el siguiente:

<wpt lat="40.412750" lon="-3.897759">
   <ele>748.43</ele>
   <time>2013-06-27T13:42:35.35Z</time>
   <extensions>
      <mlab:bearing>254.92</mlab:bearing>
      <mlab:speed>9.86</mlab:speed>
      <mlab:accuracy>12.00</mlab:accuracy>
      <mlab:ax>-0.347691</mlab:ax>
      <mlab:ay>-0.173109</mlab:ay>
      <mlab:az>0.662444</mlab:az>
      <mlab:pressure>942.38</mlab:pressure>
   </extensions>
</wpt>

Los otros elementos que puede contener un documento GPX es lo que denomina Routes y Tracks. En ambos casos se trata de colecciones ordenadas de WayPoints. La Routes están pensadas para describir un camino a recorrer. Son una colección ordenada de puntos que nos permitan describir una ruta a seguir. Un ejemplo podría ser marcar el camino a seguir para ir de Madrid a Santander, fijando los WayPoint intermedios de la Route en Tordesillas, Valladolid, Palencia y Reinosa. Otro ejemplo podría ser describir una línea de autobús, marcando los puntos intermedios en cada una de las paradas.

Por su parte, los Tracks describen un camino recorrido. Se trata de los puntos que va grabando el GPS cuando lo llevamos activado mientras recorremos el camino. Los puntos de un Track suelen llevar el dato de la fecha-hora de la medida. En realidad los Tracks no son una colección de WayPoints, sino una colección de TrackSegments, cada uno de los cuales es una colección de WayPoints. Cada Track estará compuesto por uno o más segmentos de track que son los que contendrán los puntos. Un Track tendría por tanto una geometría equivalente a la de una MultiLineString en la que cada segmento sería una LineString.

Un detalle a tener en cuenta en el esquema formal de los WayPoints es que cuando aparecen como WayPoints aislados en un documento GPX llevan la etiqueta <wpt>, mientras que cuando aparecen formando parte de una Route o de un Track llevan las etiquetas <rtept> y <trkpt> respectivamente.

Podéis encontrar la definición formal del esquema de todos los elementos que componen un documento GPX en el siguiente enlace: http://www.topografix.com/GPX/1/1/



Santiago Higuera (1 de febrero de 2014)

Los dispositivos Android disponen de un almacenamiento, denominado la ‘sdcard‘ que hace las veces de disco duro. Ahí se guardan las aplicaciones, los datos e incluso el sistema operativo. Para acceder al contenido de la sdcard se utiliza el método ‘getExternalStorageDirectory()‘ de la clase Environment

En cambio, cuando el dispositivo lleva una tarjeta de memoria adicional, no está estandarizada la manera de acceder a ella. Cada fabricante la monta en un directorio diferente. Una posible forma de arreglar el problema es el método ‘getSecondaryStorageDirectories()‘ que os proponemos a continuación. Si bien no es un método muy ortodoxo, en los dispositivos Android que tengo a mi alcance ha funcionado perfectamente. El código del método propuesto es el siguiente:

private final static String TAG = "ANDROID_UTILS";
/**
 * Recupera una lista de directorios
 * de secondary cards, si existen
 *
 * @return Lista de ficheros o una lista vacía
 */
public static List<File> getSecondaryStorageDirectories() {
    List<File> list = new ArrayList<File>();
    try {
        String cad = System.getenv("SECONDARY_STORAGE");
        String[] paths = cad.split(":");
        for(String s: paths) {
            Log.d(TAG, s);
            if(!s.contains("usb") && !s.contains("Usb")
                    && !s.contains("USB")) {
                try {
                    File dir = new File(s);
                    if(dir!=null && dir.canRead()
                            && dir.isDirectory()
                            && dir.listFiles().length>0) {
                        list.add(dir);
                    }
                } catch (Exception ignoreException) {

                }
            }
        }
    } catch (Exception ignoreException) {

    }
    return list;
}

Lleva añadidas algunas sentencias ‘Log‘ para facilitar su exploración. Hay que destacar la forma en que se discriminan los dispositivos USB. También es interesante el método ‘System.getenv()’ que nos permite explorar las variables del sistema [1]. La sentencia System.getenv(“SECONDARY_STORAGE”) nos devuelve una cadena con una serie de paths separados por dos puntos. De ahí la necesidad de discriminar los USB.

Para realizar el método getSecondaryStorageDirectories() he consultado varias páginas de internet, pero sin duda la que más me ha abierto los ojos ha sido esta de StackOverflow.

[1] Vease en ese sentido el artículo ‘System.getenv() Method – System Environment Variables’ de Dr. Herong Yang



Santiago Higuera (28 enero 2014)

Mapsforge [1] es un API de visualización de mapas de OpenStreetMap en Android que permite visualizar mapas off line, esto es, que han sido grabados previamente en el dispositivo y no requieren conexión a internet.
Los mapas tienen que estar en formato ‘.map’.
Los archivos ‘.map’ se generan a partir de los archivos ‘.osm’ de OpenStreetMap. Para crearlos nos hace falta utilizar la herramienta ‘Osmosis’ [2], a la que hay que instalarle un plugin de Mapsforge.
La documentación a la fecha es poco clara, por lo que voy a explicar aquí los pasos que he seguido yo para generar los mapas y que me han funcionado muy bien:

  • Descargar Osmosis y descomprimir en un directorio
  • Darle path al directorio bin de la instalación de Osmosis, para que podamos ejecutar la herramienta ‘osmosis’
  • Descargar las librerías Mapsforge. Son dos, la de visualizar y la de crear mapas. La de visualizar se la añadiremos a los proyectos Android. La de crear mapas es la que añadiremos a Osmosis para poder crear nuestros mapas
    • mapsforge-map-0.3.0-jar-with-dependencies.jar Esta es la de visualización en Android
    • mapsforge-map-writer-0.3.0-jar-with-dependencies.jar Esta es la que nos permite crear el plugin en Osmosis
  • Copiar la librería del map-writer en el directorio ‘lib/default’ de la instalación de Osmosis
  • Crear un fichero llamado ‘osmosis-plugins.conf’ dentro del directorio ‘config’ de la instalación de Osmosis
  • Añadirle la siguiente línea: org.mapsforge.map.writer.osmosis.MapFileWriterPluginLoader

Con esto tendremos configurado el plugin de Mapsforge para Osmosis. Ahora podemos coger un archivo ‘.osm’ y convertirlo a ‘.map’ con un comando como el siguiente:

osmosis
    --read-xml file=mimapa.osm
    --mapfile-writer file=mimapa.map
    bbox=40.0,-3.0,42.0,-1.0
    map-start-position=41.0,-2.0
    map-start-zoom=16

Una vez generado el fichero ‘.map’ podremos incorporarle al dispositivo Android y utilizar el API de Mapsforge para visualizar mapas.
Dejo en [3] enlace a un taller impartido en una de las reuniones de Geoinquietos Madrid, donde se pueden ver explicaciones más extensas así como varios ejemplos de utilización.

[1] Mapsforge http://code.google.com/p/mapsforge/
[2] Osmosis http://wiki.openstreetmap.org/wiki/Osmosis
[3] Taller Mapsforge Geoinquietos Madrid


Santiago Higuera (7 Mayo 2012)

En OpenLayers tenemos dos proyecciones definidas por defecto que son la EPSG:4326 (Coordenadas geográficas Datum WGS-84) y la EPSG::900913 (Spherical Mercator, habitualmente utilizada en los mapas de OpenStreetMap, Google y otros).

Podemos incorporar otras proyecciones. Para ello tenemos que incluir en nuestro código la librería PROJ4JS que nos permitirá definir proyecciones y realizar las correspondientes transformaciones entre ellas. La librería Proj4JS la podemos descargar desde:

http://trac.osgeo.org/proj4js/wiki/Download

Situaremos el fichero proj4js-combined.js en un directorio accesible desde nuestra página web a la que añadiremos una sentencia del tipo:

<script src="lib/proj4js-combined.js"></script>

Ahora ya podemos utilizar en nustro código Javascript la definición de proyecciones. Por ejemplo, para definir la proyección UTM zona 30 Norte, (EPSG:23030), debemos incluir en el código Javascript la siguiente sentencia:

Proj4js.defs["EPSG:23030"] = "+proj=utm +zone=30 +ellps=intl"+
    " +towgs84=-131,-100.3,-163.4,-1.244,-0.020,-1.144,9.39 "+
    " +units=m +no_defs";

También podemos incluir una proyección concreta en nuestro código mediante la siguiente construcción:

<script
    src="http://spatialreference.org/ref/epsg/23030/proj4js/">
</script>

Ahora solamente nos queda utilizar las proyecciones definidas. Por ejemplo podríamos pasar de WGS84 a UTM-30N mediante la siguiente sentencia:

var wgs84Projection = new OpenLayers.Projection("EPSG:4326");
var utmProjection = new OpenLayers.Projection("EPSG:23030");
var defaultMapCenter = new OpenLayers.LonLat(-3.7, 40.985165)
	.transform(wgs84Projection, utmProjection);

Las definiciones de las distintas proyecciones las podemos obtener en :

http://spatialreference.org/

Hemos preparado un ejemplo completo en el que cargamos la capa del Mapa Topográfico Nacional Español Raster Escala 1:25.000 y la capa de las fotografías del Plan Nacional deOrtofotografía Aérea, PNOA. El resultado lo puedes ver en :

olutm.html

Santiago Higuera (30 Marzo 2012)

Programando en Javascript es posible adoptar un enfoque orientado a objetos. De esta forma, mediante el mecanismo de ir extendiendo las clases según una jerarquía determinada, vamos obteniendo objetos adaptados a nuestras necesidades partículares que reducirán el tiempo de los desarrollos futuros.

OpenLayers es una librería Javascript que utiliza esta aproximación, mediante una jerarquía de clases que dan servicio a las diferentes funcionalidades para las que se ha pensado el programa.

Podemos utilizar la arquitectura de clases de OpenLayers para crearnos una librería de clases propia que tenga objetos tales como ‘miMapa‘, que incluye ya los controles y capas que suelo utilizar, o ‘miFeature‘ que incorpora determinadas features utilizadas por mi programa con sus propias características de visualización, por ejemplo.
Vamos a explicar en este artículo como crear una jerarquía propia de clases basada en la arquitectura proporcionada por OpenLayers.

Para ello utilizaremos primero una jerarquia simple de objetos comunes como la indicada en el siguiente esquema:




En ella definimos las clases ‘Punto‘ y ‘P3d‘. La clase Punto tiene como atributos las coordenadas x e y correspondientes a la representación de un punto en dos dimensiones. De ella deriva la clase ‘P3d‘ que añade una tercera coordenada z. La clase ‘P3d‘ hereda las propiedades y métodos de la clase ‘Punto‘, algunos de los cuales los sobreescribe.
Para implementar este esquema en nuestro programa Javascript+OpenLayers utilizaremos dos archivos:

  • Libreria js : La hemos llamado ‘clases1.js‘ y contiene las definiciones correspondientes a nuestra colección de clases.
  • Fichero html : Lo hemos llamado ‘clases1.html‘ y deberá incluir el link a la librería de OpenLayers y a nuestra librería de clases ‘clases1.js‘. Además, el fichero ‘clases1.html‘, incorpora un pequeño script donde probamos la librería.

Vamos a analizar en primer lugar la librería ‘clases1.js‘. Es conveniente en primer lugar definir nuestro propio espacio de nombres, de forma que todas nuestras clases queden ‘aisladas’ del resto de librerías que utilize el programa. Hemos elegido llamar ‘MisGeos‘ a nuestro espacio de nombres. Para definirlo se utiliza la siguiente construcción:

MisGeos = {};

A continuación definimos la clase ‘MisGeos.Punto‘. Para ello utilizaremos el objeto-función ‘OpenLayers.Class‘ definido en la librería OpenLayers. Esta es la función encargada de crear nuevas clases y de gestionar el mecanismo de herencia. Para crear una nueva clase es necesario pasarle dos paramétros: La clase de la cual deriva nuestra nueva clase y, como segundo parámetro, un array asociativo con la definición de las nuevas propiedades y métodos que incorpora el nuevo objeto:

nuevaClase = OpenLayers.Class(
    ClasePadre,
    ArrayNuevasDefiniciones
);

El array del segundo parámetro tendrá la siguiente estructura:

ArrayNuevasDefiniciones= {
    prop1: valor1,
    prop2: valor2,
    metodo1: function() {...},
    metodo2: function(..) {....}
};

Vemos que cada elemento del Array es una definición de una propiedad o método y consta del nombre, los ‘dos puntos’ y la definición correspondiente. Lo veremos más claramente con el caso concreto de nuestra clase MisGeos.Punto:

MisGeos.Punto = OpenLayers.Class(MisGeos, {
    initialize: function(xx, yy) {
        this.x = xx;
        this.y = yy;
    },
    x : null,
    y : null,
    toString : function() {
        return "("+this.x+","+this.y+")";
    }
});

Vemos que el constructor de la clase es una función especial que se debe de llamar ‘initialize‘. También hay que destacar cómo definimos las propiedades ‘x‘ e ‘y‘, a las que inicialmente asignamos un valor nulo, y cómo se procede a la asignación de sus valores. El constructor ‘initialize‘ recibe dos números ‘xx‘ e ‘yy‘ como parámetros y los asigna a las propiedades del objeto utilizando el ‘this‘. Este detalle es importante. Si no utilizamos ‘this‘, los valores serían asignados a ciertas variables ‘x‘ e ‘y‘ locales del propio método ‘initialize‘.

El método ‘toString()‘ muestra el contenido de las propiedades ‘x‘ e ‘y‘ de la instancia, encerrados entre paréntesis y separados por una coma.

Para crear una instancia de objeto de la clase MisGeos.Punto habrá que hacerlo de la siguiente manera:

var p1 = new MisGeos.Punto(-3.0,42.3);

Una vez creada la clase MisGeos.Punto, vamos a crear una clase ‘MisGeos.P3d‘ que derive de la anterior y añada una tercera coordenada. La forma de hacerlo es la siguiente:

MisGeos.P3d = OpenLayers.Class(MisGeos.Punto, {
    initialize : function(xx, yy, zz) {
        MisGeos.Punto.prototype.initialize.apply(
            this, [xx, yy]);
	this.z = zz;
    },
    z : null,
    toString : function() {
        return "("+this.x+", "+this.y+", "+this.z+")";
    }
});

Aquí es importante darse cuenta de la manera de llamar al constructor de la clase base, MisGeos.Punto, desde el constructor de la clase ‘MisGeos.P3d‘:

clasebase.prototype.initialize.apply (
    this,
    [parametros pasados al constructor]
);

Además podemos ver que se sobreescribe el método ‘toString()‘ de la clase base para adaptarlo a la nueva clase.

Con esto queda creada la librería con nuestros dos tipos de objetos ‘Punto‘ y ‘P3d‘. Para probar la librería utilizaremos un sencillo script, que se ha añadido en la página html, y que tiene la siguiente forma:

P1 = new MisGeos.Punto(-3.09, 45.78);
document.write ( "Punto P1: " + P1.toString());
P2 = new MisGeos.P3d(10,20,30);
document.write ("P3d P2: " + P2.toString());

El código completo de los dos ficheros lo puedes ver en:

En un próximo artículo aplicaremos esta metodología a la creación de clases de mapas y features personalizados.

Santiago Higuera (11 Abril 2011)

A veces necesitamos pasar algún valor contenido en una variable PHP a una variable Javascript. Podemos hacerlo de la siguiente manera:
PHP a Javascript:

<?php $mivarPhp = "Asignado en PHP"; ?>
<script type="text/javascript>
   var mivarJS ="Asignado en JS";
   alert (miVarJS);
   mivarJS=<?php echo $mivarPHP ?>;
   alert (mivarJS);
</script>

Javascript a PHP:

En el caso contrario, esto es, para pasar un valor desde Javascript a PHP podemos hacerlo de la siguiente manera:

<?php $mivarPHP=
    '<script type="text/javascript">;
    var mivarJS="Asignado en JS";
    document.writeln (mivarJS);
</script>'; ?>

Espero que os resuelva.


Angel Higuera (15 Febrero 2011)

Una feature consta de una geometria y un estilo. La geometría será una clase derivada de OpenLayers.Geometry. Estas clases tienen dos metodos ‘move’ y ‘rotate’ que permiten ajustar dinámicamente la posición de las features sobre el mapa. Las features tienen que haber sido creadas y añadidas a la capa vectorial previamente.
Una vez llamadas las funciones ‘move’ y/o ‘rotate’ hay que llamar al método ‘drawFeature()‘ de la capa para que se redibuje.

Al método ‘rotate hay que pasarle dos argumentos : un ángulo de rotación en grados (antihorario positivo) y un objeto OpenLayers.Geometry.Point que hará de centro de la rotación.

rotate: function( angle: Float, center: OpenLayers.Geometry.Point)

El método ‘move acepta como parametros el valor de los desplazamientos deseados en la dirección x e y:

move: function( x: Float, y: Float)

El ejemplo de la documentación de OpenLayers ha servido para documentar el artículo y aplica los métodos aquí expuestos a geometrías Point, LineString y LinearRing.

El método rotate no gira la feature sobre si misma, sino que realiza un giro de la feature respecto del centro de giro indicado. Si el centro de giro coincide con el centro de la feature, la feature no rota sobre si misma. Esto sucede cuando se utiliza una imagen en el estilo de una feature con geometría Point y se quiere girar sobre si misma, esto es, con centro de rotación en el centro de la feature, para orientar el icono en una determinada dirección. En este caso para girar la imagen habrá que actuar sobre la propiedad ‘style.rotation‘ de la feature, asignándole el ángulo deseado.



Santiago Higuera (14 Febrero 2011)

© 2015 Descartes Suffusion theme by Sayontan Sinha