The following classes together implement the location services provided by the MIDlet:
To implement the Utils
class:
Create the Utils.java
class file.
Assign the class to the com.nokia.example.location.touristroute
package.
package com.nokia.example.location.touristroute;
Create the Utils
class. Create a static method
for returning a String
presentation that is formatted
for a specified amount of decimals. Do not use rounding rules.
public class Utils { /** * Creates a String presentation of double value that is formatted to have a * certain number of decimals. Formatted output value does not include any * rounding rules. Output value is just truncated on the place that is * defined by received decimals parameter. * * @param value - * double value to be converted. * @param decimals - * number of decimals in the String presentation. * @return a string representation of the argument. */ public static String formatDouble(double value, int decimals) { String doubleStr = "" + value; int index = doubleStr.indexOf(".") != -1 ? doubleStr.indexOf(".") : doubleStr.indexOf(","); // Decimal point can not be found... if (index == -1) return doubleStr; // Truncate all decimals if (decimals == 0) { return doubleStr.substring(0, index); } int len = index + decimals + 1; if (len >= doubleStr.length()) len = doubleStr.length(); double d = Double.parseDouble(doubleStr.substring(0, len)); return String.valueOf(d); } }
To implement the ConfigurationProvider
class:
Create the ConfigurationProvider.java
class file.
Assign the class to the com.nokia.example.location.touristroute.model
package.
package com.nokia.example.location.touristroute.model;
Import the required classes.
import javax.microedition.location.Criteria; import javax.microedition.location.LocationException; import javax.microedition.location.LocationProvider; import javax.microedition.location.Orientation; import com.nokia.example.location.touristroute.ui.ProviderQueryUI;
Create the ConfigurationProvider
class and
the required variables.
/** * Model class that handles location providers search. */ public class ConfigurationProvider { private static ConfigurationProvider INSTANCE = null; /** Array of free Criterias. */ private static Criteria[] freeCriterias = null; /** String array of free criteria names. */ private static String[] freeCriteriaNames = null; /** Array of Criterias that may cause costs */ private static Criteria[] costCriterias = null; /** String array of non-free criteria names. */ private static String[] costCriteriaNames = null; /** Reference to ProviderQueryUI viewer class. */ private ProviderQueryUI queryUI = null; /** Selected criteria */ private Criteria criteria = null; /** Selected location provider */ private LocationProvider provider = null;
Create a static block for defining the criteria for free and
non-free location providers. The Criteria
class is
used for the selection of the location provider.
setHorizontalAccuracy
and setVerticalAccuracy
set the desired horizontal and vertical accuracy preferences. setPreferredResponseTime
sets the desired maximum response
time preference. setPreferredPowerConsumption
sets
the preferred maximum level of power consumption. setCostAllowed
sets the preferred cost setting. setSpeedAndCourseRequired
sets whether the location provider should be able to determine speed
and course. setAltitudeRequired
sets whether the
location provider should be able to determine altitude. setAddressInfoRequired
sets whether the location provider should be able to determine textual
address information.
/* * Static block that constructs the content of free and non-free Criterias. * This code block is performed before the constructor. */ static { // 1. Create pre-defined free criterias freeCriterias = new Criteria[2]; freeCriteriaNames = new String[2]; Criteria crit1 = new Criteria(); crit1.setHorizontalAccuracy(25); // 25m crit1.setVerticalAccuracy(25); // 25m crit1.setPreferredResponseTime(Criteria.NO_REQUIREMENT); crit1.setPreferredPowerConsumption(Criteria.POWER_USAGE_HIGH); crit1.setCostAllowed(false); crit1.setSpeedAndCourseRequired(true); crit1.setAltitudeRequired(true); crit1.setAddressInfoRequired(true); freeCriterias[0] = crit1; freeCriteriaNames[0] = "High details, cost not allowed"; Criteria crit2 = new Criteria(); crit2.setHorizontalAccuracy(Criteria.NO_REQUIREMENT); crit2.setVerticalAccuracy(Criteria.NO_REQUIREMENT); crit2.setPreferredResponseTime(Criteria.NO_REQUIREMENT); crit2.setPreferredPowerConsumption(Criteria.POWER_USAGE_HIGH); crit2.setCostAllowed(false); // allowed to cost crit2.setSpeedAndCourseRequired(false); crit2.setAltitudeRequired(false); crit2.setAddressInfoRequired(false); freeCriterias[1] = crit2; freeCriteriaNames[1] = "Low details and power consumption, cost not allowed"; // 2. Create pre-defined cost criterias costCriterias = new Criteria[3]; costCriteriaNames = new String[3]; Criteria crit3 = new Criteria(); crit3.setHorizontalAccuracy(25); // 25m crit3.setVerticalAccuracy(25); // 25m crit3.setPreferredResponseTime(Criteria.NO_REQUIREMENT); crit3.setPreferredPowerConsumption(Criteria.NO_REQUIREMENT); crit3.setCostAllowed(true); crit3.setSpeedAndCourseRequired(true); crit3.setAltitudeRequired(true); crit3.setAddressInfoRequired(true); costCriterias[0] = crit3; costCriteriaNames[0] = "High details, cost allowed"; Criteria crit4 = new Criteria(); crit4.setHorizontalAccuracy(500); // 500m crit4.setVerticalAccuracy(Criteria.NO_REQUIREMENT); crit4.setPreferredResponseTime(Criteria.NO_REQUIREMENT); crit4.setPreferredPowerConsumption(Criteria.NO_REQUIREMENT); crit4.setCostAllowed(true); crit4.setSpeedAndCourseRequired(true); crit4.setAltitudeRequired(true); crit4.setAddressInfoRequired(false); costCriterias[1] = crit4; costCriteriaNames[1] = "Medium details, cost allowed"; // Least restrictive criteria (with default values) Criteria crit5 = null; costCriterias[2] = crit5; costCriteriaNames[2] = "Least restrictive criteria"; }
Create the class constructor.
/** * Private constructor to force using getInstance() method. */ private ConfigurationProvider() { queryUI = new ProviderQueryUI(); }
Create a method for providing a singleton instance of the class.
/** * Provide singleton instance of this class. * * @return static instance of this class. */ public static ConfigurationProvider getInstance() { if (INSTANCE == null) { // Enable use of this class when Location API is supported. if (isLocationApiSupported()) { INSTANCE = new ConfigurationProvider(); } else { INSTANCE = null; } } return INSTANCE; }
Create a method for checking whether the Location API is supported.
/** * Checks whether Location API is supported. * * @return a boolean indicating is Location API supported. */ public static boolean isLocationApiSupported() { String version = System.getProperty("microedition.location.version"); return (version != null && !version.equals("")) ? true : false; } public LocationProvider getSelectedProvider() { return provider; }
Create a method for searching for the location provider using
the predefined criteria. The getInstance
factory
method is used to get an actual LocationProvider
implementation
based on the defined criteria. A LocationException
is thrown when a Location API specific error occurs. The getOrientation
method returns the device's current orientation.
/** * Search location provider by using pre-defined free and cost criterias. * * @param listener - * a event listener that listens ProviderStatusLisneter events. */ public void autoSearch(ProviderStatusListener listener) { try { for (int i = 0; i < freeCriterias.length; i++) { criteria = freeCriterias[i]; provider = LocationProvider.getInstance(criteria); if (provider != null) { // Location provider found, send a selection event. listener.providerSelectedEvent(); return; } } if (queryUI.confirmCostProvider()) { for (int i = 0; i < costCriterias.length; i++) { criteria = costCriterias[i]; provider = LocationProvider.getInstance(criteria); if (provider != null) { // Location provider found, send a selection event. listener.providerSelectedEvent(); return; } } } else { queryUI.showNoFreeServiceFound(); } } catch (LocationException le) { queryUI.showOutOfService(); } }
Create methods for getting the device's current orientation and determining whether orientation detection is supported.
public Orientation getOrientation() { try { return Orientation.getOrientation(); } catch (LocationException e) { return null; } } /** * Tells whether orientation is supported. * * @return a boolean indicating is orientation supported. */ public boolean isOrientationSupported() { try { // Test whether Orientation instance can be obtained. Orientation.getOrientation(); return true; } catch (LocationException e) { return false; } } }
To implement the ControlPoints
class:
Create the ControlPoints.java
class file.
Assign the class to the com.nokia.example.location.touristroute.model
package.
package com.nokia.example.location.touristroute.model;
Import the required classes.
import java.io.IOException; import java.util.Enumeration; import javax.microedition.location.Coordinates; import javax.microedition.location.Landmark; import javax.microedition.location.LandmarkException; import javax.microedition.location.LandmarkStore;
Create the ControlPoints
class. Create the
required constants and variables. The LandmarkStore
class provides methods to store, delete, and retrieve landmarks
from a persistent landmark store on the device.
/** * Model class that handles landmark store actions. */ public class ControlPoints { private LandmarkStore store = null; private static final String STORENAME = "TOURIST_DEMO"; private static final String DEFAULT_CATEGORY = null; private static ControlPoints INSTANCE = null;
Create the class constructor. The createLandmarkStore
method creates a new landmark store with a specified name. A LandmarkException
is thrown when an error related to handling
landmarks occurs.
/** * Constructs instance of this class. Makes sure that landmark store * instance exists. */ private ControlPoints() { String name = null; // Try to find landmark store called "TOURIST_DEMO". try { store = LandmarkStore.getInstance(STORENAME); } catch (NullPointerException npe) { // This should never occur. } // Check whether landmark store exists. if (store == null) { // Landmark store does not exist. try { // Try to create landmark store named "TOURIST_DEMO". LandmarkStore.createLandmarkStore(STORENAME); name = STORENAME; } catch (IllegalArgumentException iae) { // Name is too long or landmark store with the specified // name already exists. This Exception should not occur, // because we have earlier tryed to created instance of // this landmark store. } catch (IOException e) { // Landmark store couldn't be created due to an I/O error } catch (LandmarkException e) { // Implementation does not support creating new landmark // stores. } store = LandmarkStore.getInstance(name); } }
Create a method for providing a singleton instance of the class.
/** * Singleton patterns getInstance method. Makes sure that only one instance * of this class is alive at once. * * @return instance of this class. */ public static ControlPoints getInstance() { if (INSTANCE == null) { INSTANCE = new ControlPoints(); } return INSTANCE; }
Create a method for finding a landmark from the landmark store
by using an index. The Landmark
class represents
a landmark, that is, a known location with a name.
/** * Find a Landmark from the landmark store using a index. * * @param index - * the landmark in the store. * @return Landmark from landmark store. */ public Landmark findLandmark(int index) { Landmark lm = null; Enumeration cps = ControlPoints.getInstance().getLandMarks(); int counter = 0; while (cps.hasMoreElements()) { lm = (Landmark) cps.nextElement(); if (counter == index) { break; } counter++; } return lm; }
Create a method for listing all the landmarks stored in the landmark store.
The getLatitude
and getLongtitude
methods return the latitude and longitude
component of the landmark coordinate, respectively. The getQualifiedCoordinates
gets the QualifiedCoordinates
of the landmark.
/** * Find nearest landmark to coordinate parameter from the landmarkstore. */ public Landmark findNearestLandMark(Coordinates coord) { Landmark landmark = null; double latRadius = 0.1; double lonRadius = 0.1; float dist = Float.MAX_VALUE; try { // Generate enumeration of Landmarks that are close to coord. Enumeration enu = store.getLandmarks(null, coord.getLatitude() - latRadius, coord.getLatitude() + latRadius, coord .getLongitude() - lonRadius, coord.getLongitude() + lonRadius); float tmpDist; Landmark tmpLandmark = null; while (enu.hasMoreElements()) { tmpLandmark = (Landmark) enu.nextElement(); tmpDist = tmpLandmark.getQualifiedCoordinates().distance(coord); if (tmpDist < dist) { landmark = tmpLandmark; } } } catch (IOException ioe) { // I/O error happened when accessing the landmark store. return null; } return landmark; } public Enumeration getLandMarks() { Enumeration enu = null; try { enu = store.getLandmarks(); } catch (IOException ioe) { // I/O error happened when accessing the landmark store. } return enu; }
Create methods for adding, updating, and removing landmarks.
public void addLandmark(Landmark landmark) throws IOException { store.addLandmark(landmark, DEFAULT_CATEGORY); } public void updateLandmark(Landmark landmark) throws IOException, LandmarkException { store.updateLandmark(landmark); } public void removeLandmark(Landmark landmark) throws IOException, LandmarkException { store.deleteLandmark(landmark); } }
To implement the ProviderStatusListener
interface:
Create the ProviderStatusListener.java
class file.
Assign the class to the com.nokia.example.location.touristroute.model
package.
package com.nokia.example.location.touristroute.model;
Create the ProviderStatusListener
interface
and add the required notification event callbacks.
/** * Listener interface for location providers status information. */ public interface ProviderStatusListener { /** * A Notification event that location provider has been selected. */ public void providerSelectedEvent(); /** * A Notification event about the first location update. */ public void firstLocationUpdateEvent(); }
To implement the TouristData
class:
Create the TouristData.java
class file.
Assign the class to the com.nokia.example.location.touristroute.model
package.
package com.nokia.example.location.touristroute.model;
Import the required classes.
import java.util.Enumeration; import javax.microedition.location.AddressInfo; import javax.microedition.location.Coordinates; import javax.microedition.location.Landmark; import javax.microedition.location.Location; import javax.microedition.location.LocationException; import javax.microedition.location.LocationListener; import javax.microedition.location.LocationProvider; import javax.microedition.location.ProximityListener; import javax.microedition.location.QualifiedCoordinates; import com.nokia.example.location.touristroute.ui.TouristUI;
Set TouristData
to implement LocationListener
and ProximityListener
. Create the required constants
and variables.
The LocationListener
represents a listener that receives events associated with a particular LocationProvider
. The ProximityListener
is an interface representing a listener to events associated with
detecting proximity to some registered location. The Coordinates
class represents coordinates as latitude-longitude-altitude values.
/** * Model class that handles LocationListeners and ProximityListeners events. */ public class TouristData implements LocationListener, ProximityListener { /** A Reference to Tourist UI Canvas */ private TouristUI touristUI = null; /** Coordinate detection threshold radius in meters */ public static final float PROXIMITY_RADIUS = 100.0f; /** Previous coordinates outside the distance threshold area (20m) */ private Coordinates prevCoordinates = null; /** The first location update done. */ private boolean firstLocationUpdate = false; private ProviderStatusListener statusListener = null;
Create the class constructor. setLocationListener
adds a LocationListener
for updates at the specified
interval.
/** * Construct instance of this model class. */ public TouristData(ProviderStatusListener listener) { statusListener = listener; ConfigurationProvider config = ConfigurationProvider.getInstance(); // 1. Register LocationListener LocationProvider provider = config.getSelectedProvider(); if (provider != null) { int interval = -1; // default interval of this provider int timeout = 0; // parameter has no effect. int maxage = 0; // parameter has no effect. provider.setLocationListener(this, interval, timeout, maxage); } // 2. Register ProxymityListener to each Landmark found from the // Landmark store. ControlPoints cp = ControlPoints.getInstance(); Enumeration enumeration = cp.getLandMarks(); if (enumeration != null) { while (enumeration.hasMoreElements()) { Landmark lm = (Landmark) enumeration.nextElement(); createProximityListener(lm.getQualifiedCoordinates()); } } }
Create a setter method for the TouristUI
reference.
/** * Setter method to TouristUI reference. * * @param ui - * Reference to TouristUI. */ public void setTouristUI(TouristUI ui) { touristUI = ui; }
Create a method for adding a new ProximityListener
to a location provider. The addProximityListener
method adds a ProximityListener
for updates when
proximity to the specified coordinates is detected.
/** * Adds new ProximityListener to the location provider. This method is * called when constructing instance of this calss and when a new landmark * is saved by using LandmarkEditorUI. * * @param coordinates */ public void createProximityListener(Coordinates coordinates) { try { LocationProvider.addProximityListener(this, coordinates, PROXIMITY_RADIUS); } catch (LocationException e) { // Platform does not have resources to add a new listener // and coordinates to be monitored or does not support // proximity monitoring at all } }
Create an update event from LocationListener
to start a new thread in order to prevent blocking. The Location
class represents the standard set of basic location
information. The isValid
method returns whether this Location
instance represents a valid location with coordinates
or an invalid one where all the data, especially the latitude and
longitude coordinates, may not be present. The AddressInfo
class holds textual address information about a location, and the getAddress
method returns the AddressInfo
associated with this Location
object. The getSpeed
method returns the device's current ground speed
in meters per second (m/s) at the time of measurement.
/** * locationUpdated event from LocationListener interface. This method starts * a new thread to prevent blocking, because listener method MUST return * quickly and should not perform any extensive processing. * * Location parameter is set final, so that the anonymous Thread class can * access the value. */ public void locationUpdated(LocationProvider provider, final Location location) { // First location update arrived, so we may show the UI (TouristUI) if (!firstLocationUpdate) { firstLocationUpdate = true; statusListener.firstLocationUpdateEvent(); } if (touristUI != null) { new Thread() { public void run() { if (location != null && location.isValid()) { AddressInfo address = location.getAddressInfo(); QualifiedCoordinates coord = location.getQualifiedCoordinates(); float speed = location.getSpeed(); touristUI.setInfo(address, coord, speed); touristUI.setProviderState("Available"); touristUI.repaint(); } else { touristUI.setProviderState("Not valid location data"); touristUI.repaint(); } } }.start(); } }
Create a state changed event from LocationListener
to start a new thread in order to prevent blocking.
/** * providerStateChanged event from LocationListener interface. This method * starts a new thread to prevent blocking, because listener method MUST * return quickly and should not perform any extensive processing. * * newState parameter is set final, so that the anonymous Thread class can * access the value. */ public void providerStateChanged(LocationProvider provider, final int newState) { if (touristUI != null) { new Thread() { public void run() { switch (newState) { case LocationProvider.AVAILABLE: touristUI.setProviderState("Available"); break; case LocationProvider.OUT_OF_SERVICE: touristUI.setProviderState("Out of service"); break; case LocationProvider.TEMPORARILY_UNAVAILABLE: touristUI .setProviderState("Temporarily unavailable"); break; default: touristUI.setProviderState("Unknown"); break; } touristUI.repaint(); } }.start(); } }
Create a proximity event for ProximityListener
, to be called only once when the device enters the proximity of
the registered coordinates. The getAddressInfo
method
returns the AddressInfo
associated with this Location
object.
/** * proximity event from ProximityListener interface. The listener is called * only once when the terminal enters the proximity of the registered * coordinates. That's why no this method should not need to start a new * thread. */ public void proximityEvent(Coordinates coordinates, Location location) { if (touristUI != null) { touristUI.setProviderState("Control point found!"); Landmark lm = ControlPoints.getInstance().findNearestLandMark( coordinates); // landmark found from landmark store if (lm != null) { touristUI.setInfo(lm.getAddressInfo(), lm .getQualifiedCoordinates(), location.getSpeed()); } // landmark not found from the landmark store, this should not never // happen! else { touristUI.setInfo(location.getAddressInfo(), location .getQualifiedCoordinates(), location.getSpeed()); } touristUI.repaint(); } }
Create a state changed event from ProximityListener
to notify that the state of the monitoring has changed.
/** * monitoringStateChanged event from ProximityListener interface. Notifies * that the state of the proximity monitoring has changed. That's why this * method should not need to start a new thread. */ public void monitoringStateChanged(boolean isMonitoringActive) { if (touristUI != null) { if (isMonitoringActive) { // proximity monitoring is active touristUI.setProximityState("Active"); } else { // proximity monitoring can't be done currently. touristUI.setProximityState("Off"); } touristUI.repaint(); } } }
Now that you have implemented the location services, implement the UI elements.