Vous êtes sur la page 1sur 13

Skip to Content Skip to Navigation

• English

• Español

• Português

Anatomía de un MIDlet de posicionamiento


con GPS

Versión para impresión Enviar por e-mail

Una de las funcionalidades especiales de los equipos Nextel es que incluyen un chip GPS, el cual

permite obtener la latitud y la longitud en la que se ubica el equipo. En este artículo, vamos a
analizar en detalle una aplicación Java ME (J2ME) que hace uso no sólo de los APIs de

localización disponibles en los equipos Motorola iDEN que ofrece Nextel, sino también de un

mecanismo de manejo de hilos adecuado para que la experiencia de uso sea óptima.

Como por más documentación que exista, un ejemplo vale mucho, este artículo se basa casi en

su totalidad en un análisis de código fuente. Si bien no se supone que conozca cómo funciona

Java ME en su totalidad, en algo servirá para entender cuales funciones son parte de cualquier

aplicación y cuáles son parte del proceso de localización -- por lo tanto, si requiere un resumen

básico de qué es y cómo funciona Java ME, recomendamos que lea el artículo "Introducción a

J2ME" de Abel González.

Localización en pocos pasos

Sigamos pues, con el código.

La primera parte, y podríamos decir la más importante, presenta las inclusiones de los paquetes

Java ME que necesitamos:

import javax.microedition.io.*;
import java.io.*;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.lang.System;
import java.util.*;
import com.motorola.iden.position.*;

Las seis inclusiones iniciales son en general esenciales para toda aplicación Java ME que hace

algo más que mostrar una cadena de caracteres en la pantalla. La última

(com.motorola.iden.position.*) es la importante para poder hacer uso de las funciones de


localización en todos los equipos iDEN con aGPS. (También se podría usar

javax.microedition.location en los equipos que soportan el API de localización JSR-179, en cuyo

caso el código fuente sería bastante distinto y será parte de otra discusión).

Luego, viene la declaración del MIDlet propio, la cual es común a todas las aplicaciones JavaME

que utilizan un modelo de respuesta a comandos:

public class PosDemo extends MIDlet implements CommandListener {

Declaramos un objecto Calendar() para manejar las funciones de fechas:

Calendar myCalendar = Calendar.getInstance();

y una lista que utilizaremos más tarde para determinar el tipo de localización aGPS que queremos

y la tolerancia de demora que estamos dispuestos a tener:

// Lista de tolerancia de demora - tipo de conexión


String[] testList = {
"(1)Conexión: delay=no",
"(2)Conexión: delay=low",
"(3)Conexión: delay=high",
"(4)Salir",
};

Vale hablar un poco más sobre esta lista. Cada uno de los valores (delay=no, delay=low,

delay=high) se utiliza para indicarle al API de localización si queremos una localización que

requiere la precisión que proporciona el chip GPS o si podemos valernos con las coordenadas de

la celda que está dándole servicio al equipo en el momento determinado. El parámetro delay=high

le indica al API que la aplicación está dispuesta a esperar un tiempo para una coordenada precisa

-- lo que indica el uso de GPS; usando delay=no determina que, al contrario, la aplicación

requiere de un dato de manera inmediata y por lo tanto sólo recibirá las coordenadas de la celda

de servicio. Hablaremos más del parámetro delay=low luego.

Seguimos con declaraciones del Canvas, el hilo que utilizaremos para pedir las coordenadas,

variables para mantener el estado de la aplicación, los comandos que requerimos, y el

constructor de la aplicación en sí.

//Referencia a la pantalla que se asocia con esta aplicación


Display myDisplay;

// Menú principal
List myList;

// Pantalla de progreso que se usa para comunicarle el estado al


usuario
ProgressCanvas myPC;

// Hilo que usamos para obtener las coordenadas de GPS


Thread gpsThread;

// OK Command para el menú principal.


Command okCommand;

// Nos permite saber si la aplicación ya ha sido inicializada.


boolean inicializado = false;
/** Constructor para PosDemo. Generalmente es aquí donde se
instancia el
* objeto Display para la aplicación
*/
public PosDemo() {
myDisplay = Display.getDisplay(this);
}

El constructor en sí no debe ser el mecanismo para la inicialización de variables de la aplicación,

ya que el utilizarlo causa problemas en la ejecución de la aplicación. La configuración

generalmente se recomienda hacerla en startApp().

En el método startApp(), a continuación, el cual es requerido por el modelo Java ME, es el que

llama el equipo cada vez que la aplicación inicia o vuelve de una pausa. El método se puede

llamar muchas veces, así que es importante que la inicialización que exista sólo se haga cuando

se requiera.

/** Inicia la aplicación. Este método puede ser llamado multiples


veces,
* así que debe tener en cuenta no sólo la inicialización sino la
operación
* continua. El método debe regresar rápidamente ya que el
Displayable de
* cada aplicación no se muestra hasta que el método completa.
*
* @throws MIDletStateChangeException Se lanza si la aplicación no
puede comenzar
* inmediatamente pero es posible que pueda iniciar más tarde.
*/
protected void startApp() throws MIDletStateChangeException {
if( !inicializado ) {

myList = new List("Elejir prueba:", List.IMPLICIT, testList,


null);
okCommand = new Command("OK", Command.OK, 1);
myList.addCommand(okCommand);
myList.setCommandListener(this);

myPC = new ProgressCanvas();

myDisplay.setCurrent(myList);

}
}

La inicialización añade el comando a la pantalla, vincula la lista de opciones al comando, y

establece el contenido de la pantalla.

Sigue el método pauseApp() el cual es llamado cada vez que la aplicación se suspende. Este

método es esencial, ya que le permite a la aplicación soltar recursos y asegurar que pueda entrar

de manera adecuada al resumir la ejecución de la aplicación:

/** Pide a la aplicación que se detenga e ingrese al estado de pausa.


En el estado de
* pausa el MIDlet debe soltar recursos que no necesite y mantenerse
silencioso,
* aunque puede continuar con operaciones que no requieran la
pantalla
*/
protected void pauseApp() {
System.out.println("pauseApp llamado");
inicializado = true;
}

El método final del ciclo de vida de un MIDlet, destroyApp(), viene a continuación. destroyApp()

es el método que invoca el entorno Java ME en el equipo al momento de pedirle a la aplicación

que termine por completo su operación y que entre al estado Destroyed. Éste se llama cuando el

usuario termina la aplicación de manera forzosa (oprimiendo la tecla END dos veces, por

ejemplo) o cuando la aplicación misma llama la función notifyDestroyed(), la cual inicial el

proceso de término de una aplicación.

El método destroyApp() le da a la aplicación la oportunidad de terminar su operación limpiamente.

En el estado Destroyed, la aplicación debe soltar todos los recursos (conexiones de red,

conexiones al GPS, al sistema de memoria, hilos, etc.) y guardar cualquier información que deba

persistir. Este método puede ser llamado desde los estados Paused ó Active.

/**
* @param unconditional Si este parámetro es verdadero al ser llamado

* este método, la aplicación debe completar operaciones y soltar


* todos los recursos que esté usando. Si es falso, la aplicación
puede
* lanzar una excepción tipo MIDletStateChangeException para indicar
que
* no quiere ser destruída en este momento.
*
* @throws MIDletStateChangeException Lanzado si la aplicación no
desea
* terminar ahora
*/
protected void destroyApp(boolean unconditional)
throws MIDletStateChangeException {
}

A seguir, el código que maneja los comandos y las actividades que éstos iniciarán.

/** Indica que un evento de comando ha ocurrido en el Displayable d.


* <B>Nota para los desarrolladores:</B> este método debe retornar de
manera inmediata.
*
* @param c Un objeto tipo Command que identifica el comando. Éste
debe ser o uno de los comandos que se le agregaron al Displayable con
addCommand(Command) o con el comando impĺicito SELECT_COMMAND asociado
con los objetos tipo List.
* @param d El objeto tipo Displayable en el que ocurrió el evento
*/
public void commandAction(Command c, Displayable d) {

if (d == myList) {
switch (((List)d).getSelectedIndex()) {
case 0:
case 1:
case 2:
// Mostrar una barra de progreso al usuario
myDisplay.setCurrent(myPC);

// Iniciar un nuevo hilo (Thread) para que no quede


// inactiva la pantalla
gpsThread = new Thread(new
SRunnable(((List)d).getSelectedIndex()));
gpsThread.start();
break;
case 3:
// Salir de la aplicación.
notifyDestroyed();
break;
default:
break;
}

} else {
// Mostrar menu principal
myDisplay.setCurrent(myList);
}
}

El comando más importante para los propósitos de servicios de localización es el que inicializa un

nuevo hilo (gpsTread = new Thread(...)). Las funciones de localización pueden tardar, y es

importante que la aplicación continúe siendo accesible mientras las funciones de localización

retornan.

El hilo recibe como parámetro el índice escogido de la lista que definimos en el encabezado del

código. Como indicamos antes, este índice incluye un parametro que indica la tolerancia a

demoras que tiene la aplicación al esperar una posición.

Seguimos con el corazón de la aplicación, el cual usa ese parámetro y las funciones de las clases

AggregatePosition y PositionConnection para obtener las coordenadas del equipo:

/** Adquiera una conexión tipo PositionConnection de acuerdo al modo


de localización ("fix mode") que se requiere. Los modos de localización
* incluyen "no delay" (ninguna demora), "low delay" (poca demora),
and high delay ("alta demora"). Esto indica qué tolerancia tiene la
aplicación y cuánto está dispuesta a esperar por una localización.
* @param delay La demora aceptable al obtener una localización.
*/
private void getFix(int delay) {
try{
String connectionString = null;
switch(delay){
case 0:
connectionString = "mposition:delay=no";
break;
case 1:
connectionString = "mposition:delay=low";
break;
case 2:
connectionString = "mposition:delay=high";
break;
default:
connectionString = "mposition:delay=no";
break;

}
// Notificar al usuario del progreso
myPC.updateMessage("Obteniendo ubicación...");

// Obtener conexión PositionConnection y la posición


AggregatePosition
AggregatePosition pos = null;

//Iniciar una conexión de localización especificando la


demora aceptable
PositionConnection posCon =
(PositionConnection)Connector.open(connectionString);

pos = posCon.getPosition();
...

El posCon, de tipo PositionConnection es el que nos permite obtener los datos de localización del

equipo. La cadena de conexión que se le pasa al método open() de PositionConnection indica dos

cosas: primero, que la conexión es de tipo mposition, lo que le indica al equipo que debe

establecer una conexión con el chip GPS del equipo, y segundo, el parámetro delay, que indica

qué tipos de respuestas son aceptables y qué cantidad de demora se puede esperar.

En general, el equipo siempre intentará dar los datos más posibles que tenga disponibles y que

pueda obtener en tiempo menor al indicado por "delay". El parámetro "delay=no" indica que los

datos tienen que estar ya en el equipo y que sea sólo cosa de leerlos de la memoria. Esto implica

que la aplicación siempre recibira las coordenadas de la torre que le brinda servicio al equipo y

en ningún momento le dará acceso al chip GPS. Cuando el equipo está fuera de cobertura, la

latitud y longitud disponibles tendrán un valor de cero.

Cuando el parámetro es "delay=low", el equipo utilizará los datos de asistencia que haya recibido

el equipo si estos existen, y los pedirá a la red nuevamente si están caducos o no existen. Este

tipo de localización funciona dentro o fuera de la red de datos, pero tendrá más posibilidad de

obtener una localización dentro de cobertura de red. Tiene un tiempo máximo de operación de 32

segundos.

El parámetro "delay=high" le permite al equipo intentar hasta por 180 segundos el obtener una

localización GPS, sea que el equipo esté dentro o fuera de la red. El equipo en este caso utiliza

los datos de asistencia sólo si estos existen y están válidos en el equipo, y si no lo están el

equipo no intentará pedirlos a la red. Cuando se requiere de una localización autónoma (por

ejemplo, en caso donde definitivamente no hay cobertura o donde pueda demorar más de 32

segundos obtener una localización por visibilidad limitada al firmamento), éste es el método a

utilizar.

Una vez que obtenemos un resultado, debemos extraerlo del objeto AggregatePosition, donde se

introducen los valores. El objeto AggregatePosition tiene un mundo de funciones, así que para

manejarlo con más facilidad, definimos una función printPosition() que nos permite darle acceso a

estos valores. A continuación, el final de la función getFix() y la declaración de printPosition():

...
// Imprimir los datos de localización
printPosition(posCon, pos);
}catch(Exception e) {
// Notificación en caso de error
myPC.updateMessage("Excepción "+e);
}
}
/**
* Imprimir posición y otra informaciǿn
*/
private void printPosition(PositionConnection posCon,
AggregatePosition pos) {

// Establecer el formulario donde pondremos los valores para


mostrárselos al usuario.
Form myOutput = new Form("Información de posición");
myOutput.addCommand(okCommand);
myOutput.setCommandListener(this);

myPC.updateMessage("Abriendo conexión...");

try {
// Revisa para asegurar que AggregatePosition no esté nulo.
if(pos == null) {
myDisplay.setCurrent(myOutput);

myOutput.append("Objecto de Posición nulo");


return;
}

// Verifica los permisos de GPS que el usuario estableció en


su equipo
if (posCon.getStatus() ==
PositionConnection.POSITION_RESPONSE_RESTRICTED){
myDisplay.setCurrent(myOutput);

myOutput.append("Permisos restringidos en configuración


de GPS");
return;
}

// Verifica que el usuario ha dado permiso de reemplazar el


datos de alamanaque GPS
if (posCon.getStatus() ==
PositionConnection.POSITION_RESPONSE_NO_ALMANAC_OVERRIDE){
myDisplay.setCurrent(myOutput);

myOutput.append("Permisos restringidos de reemplazo de


almanaque");
return;
}

El tema de permisos es importante. El usuario de un equipo iDEN tiene la posibilidad de restringir

qué acceso tiene una aplicación a las coordenadas GPS, usando la opción de privacidad en

"Menu -> GPS" en el equipo.

(En ciertos modelos, existen funciones administrativas que permiten establecer contraseñas para

restringir quién puede modificar esa configuración.)

A seguir, verificamos los códigos de respuesta que genera el sistema en caso de errores:

// Actualizar indicador de progreso


myPC.updateMessage("Revisando errores");
Thread.currentThread().sleep(500);

// Verificar que la respuesta de AggregatePosition sea


adecuada.
// Revisar el estado de PositionConnetion
if(posCon.getStatus() !=
PositionConnection.POSITION_RESPONSE_OK &&
pos.getResponseCode() != PositionDevice.POSITION_OK) {

myDisplay.setCurrent(myOutput);

switch(pos.getResponseCode()){

case PositionDevice.FIX_NOT_ATTAINABLE:
myOutput.append("No se pudo obtener localización:
FIX_NOT_ATTAINABLE");
break;
case PositionDevice.ACCURACY_NOT_ATTAINABLE:
myOutput.append("No se pudo obtener grado de
exactitud: ACCURACY_NOT_ATTAINABLE");
break;
case PositionDevice.FIX_NOT_ATTAIN_ASSIST_DATA_UNAV:
myOutput.append("No se pudo obtener localización;
datos de asistencia no disponibles:
FIX_NOT_ATTAINABLE_ASSIST_DATA_UNAVAILBLE");
break;
case PositionDevice.ACC_NOT_ATTAIN_ASSIST_DATA_UNAV:
myOutput.append("No se pudo obtener grado de
exactitud; datos de asistencia no disponibles:
ACC_NOT_ATTAINABLE_ASSIST_DATA_UNAVAILBLE");
break;
case PositionDevice.BATTERY_TOO_LOW:
myOutput.append("Nivel de batería muy bajo:
BATTERY_TOO_LOW");
break;
case PositionDevice.GPS_CHIPSET_MALFUNCTION:
myOutput.append("Falla en el chip GPS:
GPS_CHIPSET_MALFUNCTION");
break;
case PositionDevice.ALMANAC_OUT_OF_DATE:
myOutput.append("Alamanaque GPS caduco:
ALMANAC_OUT_OF_DATE");
break;
case PositionDevice.UNAVAILABLE:
myOutput.append("Error desconocido. Si vé esto,
por favor informar a Nextel y Motorola");
break;
default:
myOutput.append("Error improbable...");
break;
}
return;
}

Si no se observaron errores, nos queda determinar qué tipo de localización obtuvimos. Si

tenemos una localización completa (es decir, una con aGPS), entonces podemos intentar extraer

todos los valores que nos brinda el objeto AggregatePosition. Si tenemos una localización de

celda, podemos extraer las coordenadas de la torre para, por lo menos, poder usar esa

información.

// Si la posición tiene latitud y longitud significa que se


inició
// la conexión con delay=low o delay=high. Imprimir datos
completos
// de posición.

if(pos.hasLatLon() ) {
// Variables de instancia usadas para generar información
para imprimir
int ano, mes, dia, hora, minuto, segundo, longD, latD,
longM, latM;
String LAT = pos.getLatitude(Position2D.DEGREES);
String LONG = pos.getLongitude(Position2D.DEGREES);
String mihora;
String horaSys;
Date mifecha= new Date(pos.getTimeStamp());
Date fechaSys = new Date(System.currentTimeMillis());

// Obtener marca de fecha y hora de localización


myCalendar.setTime(mifecha);
ano = myCalendar.get(Calendar.YEAR);
mes = myCalendar.get(Calendar.MONTH);
dia = myCalendar.get(Calendar.DAY_OF_MONTH);
hora = myCalendar.get(Calendar.HOUR_OF_DAY);
minuto = myCalendar.get(Calendar.MINUTE);
segundo = myCalendar.get(Calendar.SECOND);
mihora = ""+dia+"/"+"/"+mes+"/"+ano+"
"+hora+":"+minuto+":"+segundo;

// Obtener marca de fecha y hora actual


myCalendar.setTime(fechaSys);
ano = myCalendar.get(Calendar.YEAR);
mes = myCalendar.get(Calendar.MONTH);
dia = myCalendar.get(Calendar.DAY_OF_MONTH);
hora = myCalendar.get(Calendar.HOUR_OF_DAY);
minuto = myCalendar.get(Calendar.MINUTE);
segundo = myCalendar.get(Calendar.SECOND);
horaSys = ""+dia+"/"+"/"+mes+"/"+ano+"
"+hora+":"+minuto+":"+segundo;

// Notificación de progreso
myPC.updateMessage("Verificación completa");
Thread.currentThread().sleep(500);

// Añadir datos al formulario para mostrarlos


myOutput.append("(1) Latitud " + LAT);
myOutput.append("(2) Longitud " + LONG);
myOutput.append("(3) Altitud = " + pos.getAltitude() + "
metros ");
myOutput.append("(4) Velocidad = " + pos.getSpeed() + "
km/h ");

myOutput.append("(5) # de satélites = " +


pos.getNumberOfSatsUsed());
myOutput.append("(6) Exactitud lat/lon = " +
pos.getLatLonAccuracy() + " milímetros ");

myOutput.append("(7a) TimeMillis = " +


pos.getTimeStamp());
myOutput.append("(7b) Hora Localización= "+mytime);
myOutput.append("(7c) Hora Systema = "+ horaSys);

myOutput.append("(8) Rumbo = " +


pos.getTravelDirection());
myOutput.append("(9) Nivel de incertidumbre de velocidad
= " + pos.getSpeedUncertainty() + " km/h ");

myOutput.append("(10) Nivel de incertidumbre de altitud =


" + pos.getAltitudeUncertainty() + " milímetros \n");
myOutput.append("(11) Asistencia usada =
"+pos.getAssistanceUsed());
myOutput.append("(12) Latitud celda = "
+pos.getServingCellLatitude(Position2D.DEGREES));
myOutput.append("(13) Longitud celda = " +
pos.getServingCellLongitude(Position2D.DEGREES));

//si sólo obtuvimos localización de la celda de servicio

else {
// Notificación de progreso
myPC.updateMessage("Verificación completa");
Thread.currentThread().sleep(500);

myOutput.append("Latitud celda = "


+pos.getServingCellLatitude(Position2D.DEGREES));
myOutput.append("Longitud celda = " +
pos.getServingCellLongitude(Position2D.DEGREES));
}

}catch(Exception e) {

// Imprimir error
myOutput.append("Excepción " + e);
}finally{

// Mostrar progreso
myDisplay.setCurrent(myOutput);
}
}

Lo último que nos falta en términos de los servicios de localización es la creación de la interfaz

para el hilo que ejecutará la conexión con el sistema de localización del equipo -- SRunnable.

/**
* Hilo que usamos para obtener coordenadas de GPS.
* Usando un hilo, la interfaz permanece activa,
* evitando crear la impresión que se colgó la aplicación.
*/
class SRunnable implements Runnable {
// Modo usado para la operación del API de localización.
int mode = 0;

/** El modo que pasamos corresponde a la tolerancia de demora que


usa el
* API de localización:
* 0 = no delay
* 1 = low delay
* 2 = high delay
* @param mode La tolerancia de demora del API de localización
*/
public SRunnable(int mode) {
this.mode = mode;
}

/** Llama al método adecuado para obtener coordenadas de GPS. La


manera en la que llama el método getFix() depende de la variable "mode".
*/
public void run() {
switch(mode){
case 0:
case 1:
case 2:
getFix(mode);
break;
default:
break;
}
}
}

Por último, declaramos una clase interna, ProgressCanvas, que nos permite mostrar una barra de

progreso al usuario cuando estamos haciendo una operación que puede tomar tiempo.

/**
* Pantalla de progreso
*/
class ProgressCanvas extends Canvas implements Runnable{

boolean active;
private String message = null;
Thread t = null;
int x, y, limit, counter;
boolean expand;

ProgressCanvas(){
x = getWidth()/2;
y = getHeight()/3;
limit = 14;
expand = true;
}

/** Dibuja el Canvas en la pantalla


* @param g El objeto tipo Graphics usado para dibujar el Canvas
en la pantalla
*/
protected void paint(Graphics g){

// Limpiar la pantalla
g.setColor(0x000000);
g.fillRect(0, 0, getWidth(), getHeight());

// Actualizar el indicador de progreso


g.setColor(0xffffff);
g.drawArc(x - counter, y - counter, counter *2, counter *2,
0, 360);

if(expand){
if(counter >= limit){
expand = false;
}
counter += 2;
}else{
if(counter < 1) {
expand = true;
}
counter -= 2;
}

// Mostrar el mensaje
g.setColor(0xffffff);
g.setFont(Font.getFont(Font.FACE_PROPORTIONAL,
Font.STYLE_PLAIN, Font.SIZE_SMALL));
g.drawString(message, getWidth()/2, (getHeight()* 2)/3,
Graphics.HCENTER|Graphics.BASELINE);

/** Usado para actualizar el mensaje en el ProgressCanvas. Este


método
* no brinda ninguna funcionalidad para dividir una cadena en
múltiples
* líneas de manera automática.
* @param message El mensaje que debe ser mostrado.
*/
public void updateMessage(String message){
this.message = new String(message);
repaint();
serviceRepaints();
}

/** La implementación llama showNotify() inmediatamente antes que


este
* Canvas sea visible en la pantalla. Las subclases de Canvas
pueden
* sobrecargar este método para ejecutar ciertas oeperaciones
antes
* de ser mostradas, tales como establecer animaciones, iniciar
* contadores, etc. La implementación patrón de este método en la

* clase Canvas no contiene ninguna operación.


*/
protected void showNotify(){
active = true;
t = new Thread(this);
t.start();
}

/** La implementación llama hideNotify() justo antes que el


* Canvas sea borrado de la pantalla. Las subclases de Canvas
pueden
* sobrecargar este método para darle pausa a las animaciones,
terminar
* contadores, etc. La implementación patrón de este método en
Canvas no
* contiene ninguna operación
*/
protected void hideNotify(){
active = false;
t = null;
}

/** Cuando un objeto que implementa la interfaz Runnable es usado


para
* crear un hilo, iniciar el hilo hace que el método run() de ese
objeto
* sea llamado en ese hilo separado que se inició.
*/
public void run() {

while(active){
try{
/**
* Dibujar en la pantalla mientras el Canvas todavía
sea
* visible
*/
t.sleep(100);
if(isShown())
repaint();
}catch(Exception e){
}
}

}
}

El código completo en inglés y español se adjunta, junto con un projecto listo para compilar con el

SDK de Motorola.
Adjunto Tamaño

Adjunto Tamaño

GPS_position_sample.zip 39.55 KB

Average:

0
Your rating: Ninguno

• Java ME

• Localización y geoinformación

IR

Vous aimerez peut-être aussi