J2ME MIDlet care foloseste servicii de localizare GPS şi Google Static Map API

No comments - This post in english

Astazi, cele mai multe dispozitive mobile contin un modul GPS sau se pot conecta la unul utilizand servicii Bluetooth. Platforma Java de pe aceste dispozitive ofera suport pentru API-ul de localizare pentru J2ME definit de JSR-179, care permite dezvoltarea de aplicatii J2ME, MIDlet-uri, care pot obtine de la modulul GPS coordonatele geografice (latitudine si longitudine). De asemenea, aplicatii mobile pot integra servicii bazate pe localizare. Unul dintre serviciile cele mai folosite este furnizarea de harti cu ajutorul Google Maps sau Google Static Maps. A doua posibilitate este accesibila prin intermediul Google Maps API static V2 , care este un serviciu gratuit si deschis (nu mai necesita o cheie API Maps) este o solutie mai eficienta, deoarece minimizeaza transferuri de date in retea.

In acest articol este descrisa o aplicatie MIDlet complet functionala, care preia coordonatele GPS de la modulul dispozitivului mobil si le utilizeaza pentru a afisa o harta statica Google pentru acea locatie. Aplicatia poate fi testata pe emulator sau pe un dispozitiv real, care are un modul GPS incorporat.

Pentru a testa aplicatia se poate descarca codul sursa complet, si un MIDlet de test.

Daca aveti un dispozitiv mobil care nu are un GPS incorporat, aceasta solutie nu va functiona, deoarece API-ul de localizare pentru J2ME nu are metode pentru conectarea prin Bluetooth la acel modul extern. In acest caz, ai nevoie de o alta solutie (prezentata intr-un alt articol) care

  • utilizeaza Bluetooth API (JSR 82) pentru a descoperi si pentru a se conecta la modulul GPS extern;
  • parseaza informatiile primite in format GPS NMEA.

Cum se foloseste J2ME Location API (JSR-179) pentru a prelua coordonatele GPS intr-un MIDlet

Midlet care afiseaza coordonatele GPS

Midlet care afiseaza coordonatele GPS

Pentru a comunica cu modulul GPS al telefonului si pentru a obtine date cu privire la coordonate, directie, altitudine sau viteza trebuie sa folosim clase din pachetul javax.microedition.location.* :

  • se construieste o instanta javax.microedition.location.Criteria, utilizata pentru a indica criteriile de selectie a modulului GPS; desi sunt mai multe optiuni (descrise de documentatia API-ului JSR-179), valorile implicite sunt cele mai putin restrictive, fiind mai flexibile; in acest exemplu alegem explicit sa permitem taxarea serviciului (oricum e gratis) si nu avem cerinte legate de consumul bateriei; aceste optiuni nu sunt atat de importante pentru acest exemplu deoarece in acest exemplu,telefonul are un singur modul GPS, iar criteriile descrise de API sunt utile in situatia in care putem alege dintre mai multe module;
import javax.microedition.location.*;
...
        Criteria criteria = new Criteria();
        //valoare identica cu cea default
        criteria.setCostAllowed(true);
        //valoare identica cu cea default
        criteria.setPreferredPowerConsumption(Criteria.NO_REQUIREMENT);
  • obtinem o referinta catre modulul GPS, o instanta LocationProvider, folosind criteriile definite mai devreme; aceasta instanta este utilizata pentru a interoga modulul GPS cu privire la coordonatele locatiei curente; metoda getLocation() method are un parametru pentru timeout pentru a indica cat timp (secunde) putem astepta pentru raspunsul de la modulul GPS;
  • obtinem coordonatele si extragem latitudinea si longitudinea: ATENTIE ! coordonatele sunt reprezentate ca valori double; pentru a le putea gestiona,proiectul J2ME trebuie sa fie configurat pentru un dispozitiv cu configuratia CLDC 1.1 (CLDC 1.0 nu suporta Double).
LocationProvider provider = null;
double latitude;
double longitude;
try {
	//obtinem referinta catre modulul GPS
        provider = LocationProvider.getInstance(criteria);
	//setare timeout la 60 seconds
        Location location = provider.getLocation(60);
	//obtinem coordonatele
        Coordinates coordinates = location.getQualifiedCoordinates();
 
        if (coordinates != null) {
            //obtinem latitudinea si longitudinea
            latitude = coordinates.getLatitude();
            longitude = coordinates.getLongitude();
        } else {
            //nu am obtinut coordonatele
        }
    } catch (LocationException ex) {
        System.out.println("Probleme cu modulul GPS ! " +
		ex.getMessage());
        ex.printStackTrace();
    } catch (InterruptedException ex) {
	System.out.println(ex.getMessage());
        ex.printStackTrace();
    }

In aplicatia finala (din arhiva), secventa anterioara de cod este parte din metoda getGPSData(). Aceasta metoda defineste o clasa interna care extinde Thread deoarece ne dorim sa interogam modulul GPS pe un alt fir de executie decat cel principal. Astfel, aplicatia J2ME va putea raspunde la comenzi, cum ar fi Exit, in timp ce asteapta raspunsul de la modulul GPS.

Cum sa obtinem coordonatele GPS la intervale regulate intr-un MIDlet

Daca dorim sa dezvoltam o aplicatie J2ME care sa primeasca coordonatele GPS la intervale regulate (nu este cazul acestui exemplu) trebuie definit un handler (functie) utilizat de LocationProvider pentru a notifica aplicatia cu privire la modificarea coordonatelor. Pentru a face acest lucru, trebuie implementata interfata LocationListener care are 2 metode abstracte:

  • locationUpdated(LocationProvider provider, Location location) – metoda apelata de provider (modulul GPS) la intervale regulate pentru a furniza locatia curenta;
  • providerStateChanged(LocationProvider provider, int newState) – metoda apelata de provider pentru a anunta starea modulului (LocationProvider.OUT_OF_SERVICE, LocationProvider.AVAILABLE, LocationProvider.TEMPORARILY_UNAVAILABLE);
public void locationUpdated(LocationProvider arg0, Location arg1) {
        if (arg1 != null && arg1.isValid()) {
            //obtinem coordonatele
            Coordinates coordinates = arg1.getQualifiedCoordinates();
 
            if (coordinates != null) {
                //obtinem latitudinea si longitudinea
                latitude = coordinates.getLatitude();
                longitude = coordinates.getLongitude();
 
            } else {
                //nu am obtinut coordonatele
            }
        }
    }
 
    public void providerStateChanged(LocationProvider arg0, int arg1) {
        if (arg1 == LocationProvider.OUT_OF_SERVICE ||
                arg1 == LocationProvider.TEMPORARILY_UNAVAILABLE) {
            System.out.println("GPS inactive");
        }
    }


Pentru a inregistra handler-ul si pentru a primi actualizari cu privire la locatie se foloseste metoda setLocationListener(LocationListener listener,int interval,int timeout,int maxAge):

	provider.setLocationListener(this, 60, -1, -1);

 

Pentru acest apel, aplicatia primeste date de la GPS la fiecare 60 secunde cu timeout si maxAge avand valori default.

Daca dorim sa oprim aceste update-uri (consuma bateria mai repede), se apeleaza iar metoda setLocationListener() cu parametrii:

	provider.setLocationListener(null, -1, -1, -1);

 

Cum se foloseste Google Static Map API

Google Static Maps este un serviciu gratis oferit de Google dezvoltatorilor software care doresc sa integreze harti in aplicatiile lor. Acest lucru se poate face prin intermediul parametrilor unei adrese URL, trimisa printr-o cerere simpla HTTP fara a fi necesara utilizarea de JavaScript sau incarcarea de pagini dinamice. Serviciul Google Static Map trimite imaginea ca raspuns.

Deci, pentru a obtine imaginea 300×300 pixeli a unei harti pentru coordonatele 44.435251 (latitudine) si 26.1024 (longitudine), cu un nivel de zoom moderat, se face o cerere HTTP folosind string-ul:

http://maps.google.com/maps/api/staticmap?center=44.435251,26.1024&zoom=14&size=300×300&sensor=false

Rezultatul (imaginea oferita de serviciul Google) este:

Exemplu Google Static Map 300x300

Exemplu Google Static Map 300x300

Pentru alte optiuni ca pot fi folosite pe harti, cum ar fi markarea unor puncte sau modificarea tipului hartii), verificati documentatia oficiala: Static Maps API V2 Developer Guide.

Cum se descarca si cum se afiseaza o imagine Google Static Map pe dispozitivul mobil

MIDlet care afiseaza o harta Google Static

MIDlet care afiseaza o harta Google Static

Aplicatia J2ME MIDlet primeste coordonatele de la modulul GPS si le foloseste pentru a construi cererea HTTP. Imaginea este descarcata si apoi este afisata pe un formular de tip Canvas:

  • formularul utilizat pentru a afisa imaginea este de tip Canvas (in J2ME, Canvas si GameCanvas sunt singurele tipuri de formulare care permit accesul la referinta Graphics reference necesara pentru a desena);
  • textul cererii HTTP este generata utilizand coordonatele (latitudine, longitudine), nivelul de zoom; acestea sunt definite la nivelul formularului ca variabile de instanta (atribute);
public class GoogleMaps extends Canvas implements CommandListener {
 
    Command cmdBack = new Command("Back", Command.EXIT, 1);
    Command cmdRefresh = new Command("Refresh", Command.SCREEN, 10);
 
    //referinta catre MIDlet
    MidletGPS midGPS;
    int zoom = 14;		//valoare default pentru zoom
    String latitude = "";
    String longitude = "";
 
    public GoogleMaps(MidletGPS mGPS, String Lat, String Longit) {
        latitude = Lat;
        longitude = Longit;
        midGPS = mGPS;
 
        this.addCommand(cmdBack);
        this.addCommand(cmdRefresh);
 
        this.setCommandListener(this);
    }
 
...
}
  • imaginea este descarcata utilizand o instanta de tip HttpConnection, care gestioneaza conexiunea HTTP, si un InputStream pentru a obtine forma serializata a imaginii;
  • odata ce stream-ul a fost obtinut, imaginea este construita direct folosind metoda Image.createImage(InputStream); o alternativa (solutia din comentarii) este utilizarea unui sir de bytes;
private Image getGoogleMap() {
    HttpConnection connection = null;
    InputStream inputStream = null;
 
    //obtinem latimea si inaltimea ecranului
    int width = this.getWidth();
    int height = this.getHeight();
 
    //string-ul cererii HTTP pentru serviciul Google
    String url = "http://maps.google.com/maps/api/staticmap?center=";
    url += this.latitude + "," + this.longitude;
    url += "&zoom=" + String.valueOf(zoom);
    url += "&size=" + width + "x" + height + "&sensor=true";
 
    try {
        connection = (HttpConnection) Connector.open(url);
        connection.setRequestMethod(HttpConnection.GET);
        inputStream = connection.openInputStream();
 
        Image map = Image.createImage(inputStream);
 
//	      //solutie alternativa
//            ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
//            //obtinem imaginea byte cu byte
//            int c;
//            while ((c = inputStream.read()) != -1) {
//                byteArray.write(c);
//            }
//            byte[] buffer = byteArray.toByteArray();
//            byteArray.close();
//
//            //construim un obiect Image pe baza sirului de bytes
//            logo = Image.createImage(buffer, 0, buffer.length);
 
        return map;
 
    } catch (Exception ex) {
        ex.printStackTrace();
    } finally {
        try {
            if (inputStream != null) {
                inputStream.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    return null;
}
  • imaginea este afisata pe toata suprafata Canvas-ului supradefinind metoda paint a clasei Canvas;
    protected void paint(Graphics g) {
        //obtinem imaginea
        Image map = getGoogleMap();
        if(map!=null){
            //desenam imaginea pe canvas
            g.drawImage(map, 0, 0, Graphics.LEFT | Graphics.TOP);
        }
        else{
            g.setColor(255,0,0);
            g.drawString("Harta nu este disponibila", 20, 20, 0);
        }
    }
  • daca se doreste modificarea nivelului de zoom (+ sau –) din aplicatie, putem defini un handler (functie) pentru evenimentul key pressed; acest lucru este posibil prin supradefinirea metodei void keyPressed(int keyCode); in acest examplu, folosim tasta 1 pentru a face zoom out si tasta 3 pentru a face zoom in:
    protected void keyPressed(int keyCode) {
        if (((char) keyCode) == '1') {
            zoom--;
        }
        if (((char) keyCode) == '3') {
            zoom++;
        }
	//se apeleaza evenimentul paint
        this.repaint();
    }
  • in metoda keyPressed() se poate modifica  longitudinea si latitudinea cu 0.02 grade si sa se ceara o noua imagine; acest lucru permite utilizatorilor sa se mute in toate directiile folosind alte chei (codul este disponibil in arhiva exemplului complet de MIDlet)

Cum se testeaza aplicatia in Netbeans folosind un emulator GPS

Emultor GPS emulator in External Events Generator

Emultor GPS emulator in External Events Generator

Daca se doreste testarea aplicatiei GPS MIDlet in emulatorul oferit de NetBeans (Java Platform Micro Edition SDK 3.0) este nevoie de un modul care sa emuleze modulul. Emulatorul odera acest lucru prin External Event Generator.

1. se deschide External Event Generator folosind optiunea View a emulatorului de telefon mobil din NetBeans;

2. se completeaza datele GPS; daca se doreste generarea maimultor valori GPS, defineste un fisier XML si incarca-l cu butonul Browse; un exemplu de fisier de configurare XML poate fi:

<waypoints>
  <waypoint altitude="100" longitude="26.1024" latitude="44.435251" time="0" />
  <waypoint altitude="0" longitude="24.1024" latitude="46.435251" time="10000" />
</waypoints>

3. se activeaza optiunea de trimitere a datelor pentru a fi receptionate de aplicatia MIDlet;

Pentru a testa aplicatia se poate descarca codul sursa complet, si un MIDlet de test. Exemplul din arhiva permite utilizatorilor sa se deplaseze in toate directiile pe harta.

Pentru un alt exemplu de solutie J2ME care utilizeaza date GPS si servicii Web Google Maps, puteti citi Developing Location Based Services: Introducing the Location API for J2ME

, , ,


  1. No comments yet.
(will not be published)

  1. No trackbacks yet.