Ci occupiamo in questo articolo di Location API una libreria Java ME per la realizzazione di applicazioni location-based. Tramite questa API è possibile realizzare applicazioni che calcolino la posizione di un dispositivo radiomobile nello spazio geografico.
Esistono varie tecnologie per il calcolo della posizione di un dispositivo radiomobile: ognuna di queste si caratterizza per la precisione che è in grado di fornire, per il tipo di rete alla quale è applicabile, per la richiesta o meno di un qualche supporto (hardware/software) da parte del terminale stesso. Vediamo una breve panoramica delle tecniche più utilizzate.
Il sistema più semplice è il Cell-ID che si basa sulla posizione (nota) della cella per localizzare il dispositivo. Risulta evidente che l‘approssimazione della posizione sarà tanto migliore quanto più ristretta sarà la superficie coperta da ogni singola cella. Per questa ragione il Cell-ID risulta molto impreciso nelle aree extraurbane dove le celle coprono superfici dell‘ordine di km. Nella versione Timing Advance, oltre all‘identificativo della cella viene valutata la distanza radiale del dispositivo dall‘antenna della cella (tempo di propagazione del segnale) aumentandone così la precisione.
I sistemi EOTD (Enhanced Observed Time Difference) e TDOA (Time Difference Of Arrival) si basano sulla triangolazione ed utilizzano il ritardo di propagazione del segnale dall‘apparato radio-mobile a quello radio-base per la stima della posizione. Ogni dispositivo mobile riceve contemporaneamente il segnale proveniente da più celle: in base ai ritardi di propagazione misurati o alla differenza tra i ritardi stessi è possibile stimare con buona approssimazione la posizione dell‘apparato. Si tratta, più o meno della stessa tecnica utilizzata dal sistema GPS con la differenza che, mentre quest‘ultimo utilizza una costellazione di satelliti (24 in 6 diverse orbite) come “punti noti”, nell‘EOTD e nel TDOA i ritardi vengono calcolati con le celle della rete.
La versione “assistita” del GPS prevede il supporto della rete radio-mobile in modo da diminuire i tempi di localizzazione. Ogni cella occupa, infatti, una posizione nota in termini di latitudine, longitudine che costituiscono la base di partenza per il calcolo del GPS. Il GPS, in questo caso, conosce già , seppure in modo approssimato, la posizione del dispositivo e questo rende più veloce una localizzazione accurata (riducendo i tempi della cosiddetta “partenza a freddo” del GPS).
Il sistema AOA (Angle Of Arrival) prevede l‘utilizzo di “antenne intelligenti” (antenne “settoriali”) nelle celle della rete. In questo caso la posizione degli apparati radio-mobili viene stimata valutando l‘angolo di arrivo del segnale in almeno tre diverse celle.
Nei sistemi AFLT (Advanced Forward Link Trilateration ) e EFLT (Enhanced Forward Link Trilateration), il sistema di calcolo della posizione è implementato direttamente sul dispositivo mobile. I tempi di ritardo del segnale tra stazione-base e l‘apparato mobile vengono calcolati direttamente da quest‘ultimo. Questo rende necessario un meccanismo di sincronizzazione tra l‘apparato e la cella.
La tabella che segue (figura 1) riassume quanto detto.
Vediamo ora come si può sviluppare un‘applicazione location-based in java-midp 2.0. A questo scopo esiste un package opzionale di Java ME denominato Location API. Si tratta di una libreria che si integra con MIDP versione 2.0 mentre non è compatibile con la 1.0 in quanto richiede il supporto dei numeri floating-point, caratteristica non presente nella prima versione MIDP. Cuore dell‘API è la classe LocationProvider che permette di ottenere, in modo del tutto trasparente (per quando riguarda il sistema di localizzazione utilizzato, a basso livello, dall‘apparato), informazione sulla posizione dell‘apparato in un dato momento.
Il riferimento al provider si ottiene con una chiamata al metodo getInstance. Dato che in un apparato possono coesistere più sistemi di localizzazione, si possono avere provider diversi in base al particolare sistema. Per questa ragione il metodo getInstance prevede un parametro di tipo Criteria:
public static LocationProvider getInstance(Criteria criteria) throws LocationException
utilizzato per la scelta del tipo del fornitore del servizio di localizzazione che si desidera utilizzare:
Criteria criteria = new Criteria(); criteria.setCostAllowed(true); criteria.setAltitudeRequired(true); ................................................... ................................................... LocationProvider provider = LocationProvider.getInstance(criteria);
La scelta del provider può avvenire in base a numerosi criteri (tabella in figura 2):
Il metodo di factory getInstance restituisce il provider che meglio soddisfa tali criteri, oppure null se nessun sistema presente possiede le caratteristiche richieste. Una volta ottenuto il riferimento al LocationProvider lo si può interrogare per ottenere la posizione dell‘apparato nell‘istante in cui viene effettuata la richiesta:
public Location getLocation(int timeout)throws LocationException, InterruptedException
L‘oggetto Location contiene informazioni relative alle coordinate geografiche del punto in cui ci si trova, che possono essere lette tramite il metodo
QualifiedCoordinates getQualifiedCoordinates()
QualifiedCoordinates è un‘estensione della classe Coordinates e rappresenta le coordinate geografiche di un punto (longitudine – latitudine – altitudine) e la relativa accuratezza:
public QualifiedCoordinates(double latitude, double longitude, float altitude, float horizontalAccuracy, float verticalAccuracy)
Le coordinate sono espresse nello standard WGS84 (World Geodetic System 1984). La latitudine varia tra +90.0 e -90.0: valori positivi indicano un punto nell‘emisfero nord mentre quelli negativi si riferiscono a punti dell‘emisfero sud. La longitudine assume valori compresi tra +180 e -180 man mano che ci si sposta da est verso ovest. L‘altitudine è espressa in metri e rappresenta l‘altezza dell‘ellissoide definito dallo standard WGS84 (figura 3).
Oltre alle informazioni relative alle coordinate spaziali, potrebbe essere utile conoscere la velocità di spostamento dell‘apparato radiomobile e il suo orientamento rispetto al nord terrestre. Per ottenere tali dati la classe Location fornisce i metodi:
public float getSpeed()
public float getCourse()
Il primo restituisce appunto la velocità con cui l‘apparato si sta muovendo espressa in m/s, mentre getCourse() ritorna un valore compreso tra 0.0 e 360 gradi riferito al nord terrestre.
Il metodo:
public int getLocationMethod()
restituisce il metodo di localizzazione utilizzato per stabilire la location. Il valore ritornato da questo metodo è il risultato di un OR bit a bit tra tre diverse componenti:
- tecnologia utilizzata per la localizzazione (MTE_ANGLEOFARRIVAL, MTE_CELLID, MTE_SATELLITE, MTE_SHORTRANGE, MTE_TIMEDIFFERENCE, MTE_TIMEOFARRIVAL);
- metodo utilizzato (MTY_NETWORKBASED, MTY_TERMINALBASED);
- grado di assistenza (MTA_ASSISTED, MTA_UNASSISTED). Nel caso di sistemi assisted la valutazione della locazione viene effettuata con l‘ausilio della controparte: assistenza da parte del terminale per i sistemi “network based”; viceversa, nei sistemi di tipo “terminal based” l‘aiuto è fornito dalla rete.
Per ottenere le informazioni sulla posizione si può, in alternativa alla lettura diretta, utilizzare il listener LocationListener al quale viene notificato il cambiamento di locazione con l‘invocazione del metodo:
public void locationUpdated(LocationProvider provider, Location location)
Il listener possiede inoltre il metodo:
void providerStateChanged(LocationProvider provider, int newState)
per la notifica di eventuali cambiamenti di stato del provider del servizio di localizzazione (AVAILABLE, OUT_OF_SERVICE, TEMPORARILY_UNAVAILABLE).
Il listener deve essere agganciato al location provider con il metodo:
public void setLocationListener(LocationListener listener, int interval, int timeout, int maxAge)
dove
- listener è l‘ascoltatore
- interval è il lasso di tempo tra le notifiche (-1 indica il valore di default, 0 indica che l‘applicazione è interessata solo al monitoring dello stato del provider e non all‘”ascolto” della locazione)
- timeout è il tempo massimo entro il quale si desidera ottenere un nuovo valore della locazione (il valore -1 indica il timeout di default). Nel caso il provider non sia in grado di fornire un nuovo valore di locazione entro il valore di timeout impostato verrà ritornata una locazione non valida.
- La validità della locazione è ottenibile con il metodo public boolean isValid() della classe Location.
- maxAge, serve per stabilire il periodo di validità di una locazione. Questo può essere utile quando il sistema di localizzazione risponde con tempi abbastanza lunghi mentre l‘applicazione non necessita di aggiornamenti molto frequenti. Allungando i tempi di maxAge si fa in modo che il sistema ritorni un locazione letta in precedenza se non sono disponibili dati aggiornati. Settando maxAge a -1 viene utilizzato il valore (maxAge massimo) di default.
La porzione di codice che segue mostra una semplice implementazione del listener che stampa le informazioni sulla location e sullo stato del provider.
............................................ // Definisce un criterio per la ricerca del provider Criteria criteria = new Criteria(); criteria.setCostAllowed(true); criteria.setSpeedAndCourseRequired(true); criteria.setPreferredPowerConsumption(Criteria.POWER_USAGE_MEDIUM); ............................................ // Recupera un riferimento al provider LocationProvider provider = LocationProvider.getInstance(criteria); // Setta un ascoltatore sul provider (i valori -1 indicano valori di default) provider.setLocationListener(new MyLocationListener(), -1, -1, -1); ............................................ ............................................ // Implementazione di test del listener. // Stampa le informazione relative alla Location del dispositivo e allo stato del provider. public class MyLocationListener implements LocationListener { public void locationUpdated(LocationProvider provider, Location location) { QualifiedCoordinates qualifiedCoordinate = location.getQualifiedCoordinates(); System.out.println( "/************* Location Event*******************/"); System.out.println("Location - Latitude: " + qualifiedCoordinate.getLatitude()); System.out.println("Location - Longitude: " + qualifiedCoordinate.getLongitude()); System.out.println("Location - Altitude: " + qualifiedCoordinate.getAltitude()); System.out.println("Location - Speed: " + location.getSpeed()); System.out.println("Location - Course: " + location.getCourse()); System.out.println("Location - Timestamp: " + new Date(location.getTimestamp())); try { Orientation orientation = Orientation.getOrientation(); System.out.println("Orientation - Compass Azimuth: " + orientation.getCompassAzimuth()); System.out.println("Orientation - Pitch: " + orientation.getPitch()); System.out.println("Orientation - Roll: " + orientation.getRoll()); System.out.println("Orientation - Orientation Magnetic: " +orientation.isOrientationMagnetic()); } catch (LocationException e) { e.printStackTrace(); ... } } public void providerStateChanged(LocationProvider provider, int newState) { switch (newState) { case LocationProvider.AVAILABLE: System.out.println("Location provider available"); break; case LocationProvider.OUT_OF_SERVICE: System.out.println("Location provider out of service"); break; case LocationProvider.TEMPORARILY_UNAVAILABLE: System.out.println("Location provider temporarily unavailable"); break; } } }
Oltre alla notifiche relative al cambiamento di locazione, può essere utile in taluni casi una notifica più “intelligente”, qualcosa in grado di avvertirci che siamo giunti in prossimità di un determinato punto (ristorante, parcheggio, ecc.). La classe LocationProvider ha, a questo proposito, un apposito metodo:
public static void addProximityListener(ProximityListener listener, Coordinates coordinates, float proximityRadius)
che permette appunto di ottenere una notifica, tramite il ProximityListener, quando il dispositivo si viene a trovare a una distanza inferiore a proximityRadius dal punto avente coordinate coordinates (figura 4).
Il codice che segue mostra una semplice implementazione di test del ProximityListener.
............................................ // Definisce un criterio per la ricerca del provider Criteria criteria = new Criteria(); criteria.setCostAllowed(true); LocationProvider provider = LocationProvider.getInstance(criteria); // Coordinate di riferimento (sono le coordinate del punto di interesse) Coordinates refCoordinates = new Coordinates(43.77916, 11.26621, Float.NaN); // Aggiunge il listener MyProximityListener. La notifica avverrà quando il dispositivo // verrà a trovarsi in un raggio di 100 metri dal punto di coordinate refCoordinates provider.addProximityListener(new MyProximityListener(), refCoordinates, 100); // Setta il location listener provider.setLocationListener(new MyLocationListener(), -1, -1, -1); ............................................ ............................................ // Implementazione di test del proximity listener. // Stampa le informazione relative alla Location del dispositivo quando questo // si viene a trovare in prossimità del punto di coordinate coordinates public class MyProximityListener implements ProximityListener { public void monitoringStateChanged(boolean isMonitoringActive){ System.out.println("Monitoring state changed: "+isMonitoringActive); } public void proximityEvent(Coordinates coordinates, Location location) { QualifiedCoordinates qualifiedCoordinate = location.getQualifiedCoordinates(); System.out.println("/******************** Proximity Event ********************/"); System.out.println("Location - Latitude: " + qualifiedCoordinate.getLatitude()); System.out.println("Location - Longitude: " + qualifiedCoordinate.getLongitude()); System.out.println("Location - Altitude: " + qualifiedCoordinate.getAltitude()); System.out.println("Location - Speed: " + location.getSpeed()); System.out.println("Location - Course: " + location.getCourse()); System.out.println("Location - Timestamp: " + new Date(location.getTimestamp())); } }
Oltre alla localizzazione, le Location API permettono di conoscere anche i dati relativi all‘orientamento del dispositivo tramite la classe Orientation. Per ottenere tali informazioni occorre invocare il metodo statico:
public static Orientation getOrientation() throws LocationException
che restituisce, appunto, un oggetto di tipo Orientation. Quest‘ultimo oggetto dispone di tre proprietà relative all‘orientamento:
- compass azimuth
- roll (rollio)
- pitch (beccheggio)
Compass azimuth è la misura dell‘angolo tra il dispositivo e il vero nord o il nord magnetico. Il vero nord, detto anche polo nord terrestre, è il punto geografico in cui l‘asse di rotazione della terra interseca la superficie terrestre nell‘emisfero nord. Il polo nord magnetico invece è il punto dell‘emisfero nord in cui il campo magnetico terrestre forma un angolo retto con la superficie delle terra. Questi due punti, pur trovandosi localizzati nella zona artica, non coincidono tra essi e occorre pertanto sapere a quale dei due nord ci si sta riferendo.
Per valutare se il valore restituito da getCompassAzimuth si riferisce a polo nord terrestre o a quello magnetico si utilizza il metodo:
public boolean isOrientationMagnetic()
che ritorna true se il valore è riferito al polo nord magnetico, false se la misura è relativa al polo nord geografico. Il valore dell‘azimuth può variare tra 0 ° e 360 °. I valori 0 °, 90 °, 180 ° e 270 ° indicano, rispettivamente, il nord, l‘est, il sud e l‘ovest.
Il metodo:
public float getRoll()
restituisce la misura dell‘angolo di rotazione del terminale rispetto al suo asse longitudinale. Tale angolo può assumere valori compresi tra -180 ° e +180 °: un roll negativo indica che il dispositivo è orientato in senso antiorario rispetto alla posizione di equilibrio, al contrario valori positivi indicano una rotazione in senso orario (figura 6).
public float getPitch()
fornisce l‘inclinazione del terminale (figura 7). Più precisamente il pitch è la misura dell‘angolo formato dal dispositivo con un piano passante per il suo asse longitudinale e ortogonale alla superficie terrestre. I valori del pitch variano in un range che va da -90 ° a + 90 °; valori negativi indicano un orientamento della parte superiore del dispositivo verso terra mentre valori positivi indicano un‘inclinazione del terminale verso l‘alto.
Conoscere la posizione di un dispositivo in termini di longitudine-latitudine-altitudine non è di per se un‘informazione molto significativa: tali dati devono essere elaborati dalle varie applicazioni per fornire all‘utente finale un determinato servizio, ad esempio la ricerca dell‘autofficina più vicina o un servizio di navigazione. In pratica, quindi, nelle applicazioni reali è importante conoscere anche ciò che ci circonda in modo da fornire all‘utente un vero e proprio servizio. La localizzazione del dispositivo non basta: per fornire un servizio occorre fornire anche la collocazione di punti di interesse cioè la posizione di luoghi o cose che l‘utente finale desidera raggiungere (un museo, un ristorante, l‘indirizzo di un amico). Le Location API dispongono a questo proposito di un insieme di classi che permettono di geo-referenziare “punti di riferimento” e di salvarli in modo persistente sul dispositivo. La classe Landmark unisce appunto a dati relativi alla collocazione geografica (QualifiedCoordinates) dati descrittivi del punto di interesse: nome, descrizione, indirizzo (via, numero civico, città , nazione etc.). La creazione di un Landmark avviene per mezzo del costruttore
public Landmark(String name, String description, QualifiedCoordinates coordinates, AddressInfo addressInfo)
al quale vengono passati il nome, la descrizione, le coordinate geografiche e informazioni sull‘indirizzo del punto di riferimento. I Landmark possono essere salvati in modo non volatile in un apposito record store chiamato LandmarkStore. Il metodo statico
public static void createLandmarkStore(String storeName) throws IOException, LandmarkException
permette la creazione di un nuovo LandmarkStore al quale viene assegnato il nome storeName.
Dal punto di vista logico lo store è organizzato in categorie:
public void addCategory(String categoryName) throws IOException, LandmarkException
public void deleteCategory(String categoryName) throws IOException, LandmarkException
permettono, rispettivamente, l‘aggiunta la rimozione di una categoria dallo store mentre,
public Enumeration getCategories()
restituisce l‘elenco delle categorie contenute nel LandmarkStore.
Le operazioni sui landmark sono anch‘esse riferite ad una determinata categoria. I metodi:
public void addLandmark(Landmark landmark, String category) throws IOExceptionpublic void removeLandmarkFromCategory(Landmark landmark, String category) throws IOException
public Enumeration getLandmarks(String category, String name) throws IOException
permettono l‘aggiunta, la rimozione e la lettura dei punti di interesse.
In una applicazione reale risulta importante poter filtrare i landmark contenuti nello store in base alle loro coordinate geografiche, cioè poter trovare i punti di riferimento più vicini a una determinata locazione. Questa operazione è possibile con l‘invocazione del metodo:
public Enumeration getLandmarks(String category, double minLatitude, double maxLatitude, double minLongitude, double maxLongitude) throws IOException
che restituisce tutti i punti di riferimento appartenenti a una data categoria che si trovano in un punto di latitudine compresa tra minLatitude e maxLatitude e di longitudine compresa nell‘intervallo minLongitude – maxLongitude.
Durante le fasi di sviluppo e di testing di un una applicazione location-based che utilizza le Location API, può essere utile disporre di un “emulatore di localizzazione” in grado di simulare il comportamento di un dispositivo reale. L‘emulatore fornito con il wireless toolkit di Sun Microsystem prevede a questo scopo la possibilità di generare eventi esterni tramite l‘”External Event Generator”. Per aprire la schermata di questo generatore di eventi occorre cliccare su MIDlet nella finestra dell‘emulatore in esecuzione, poi scegliere External events come visibile in figura 8:
A questo punto si aprirà una finestra come quella riportata in figura 9.
In questa finestra si possono inserire sia valori relativi all‘orientamento (azimuth, pitch, roll) sia dati di collocazione geografica (latitudine, longitudine, altitudine): ogni valore inserito viene inviato all‘emulatore in esecuzione. Un‘applicazione location-based, che utilizza le Location API, in esecuzione nell‘emulatore, tratterà ogni dato inserito nel generatore di eventi come se arrivasse da un dispositivo di localizzazione reale.