Implementing the location services

The following classes together implement the location services provided by the MIDlet:

Utils class

To implement the Utils class:

  1. Create the Utils.java class file.

  2. Assign the class to the com.nokia.example.location.touristroute package.

    package com.nokia.example.location.touristroute;
  3. 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);
        }
    }

ConfigurationProvider class

To implement the ConfigurationProvider class:

  1. Create the ConfigurationProvider.java class file.

  2. Assign the class to the com.nokia.example.location.touristroute.model package.

    package com.nokia.example.location.touristroute.model;
  3. 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;
  4. 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;
  5. 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";
        }
  6. Create the class constructor.

        /**
         * Private constructor to force using getInstance() method.
         */
        private ConfigurationProvider()
        {
            queryUI = new ProviderQueryUI();
        }
  7. 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;
        }
  8. 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;
        }
  9. 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();
            }
        }
  10. 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;
            }
        }
    }

ControlPoints class

To implement the ControlPoints class:

  1. Create the ControlPoints.java class file.

  2. Assign the class to the com.nokia.example.location.touristroute.model package.

    package com.nokia.example.location.touristroute.model;
  3. 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;
  4. 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;
  5. 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);
            }
        }
  6. 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;
        }
  7. 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;
        }
  8. 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;
        }
  9. 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);
        }
    
    }

ProviderStatusListener interface

To implement the ProviderStatusListener interface:

  1. Create the ProviderStatusListener.java class file.

  2. Assign the class to the com.nokia.example.location.touristroute.model package.

    package com.nokia.example.location.touristroute.model;
  3. 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();
    }

TouristData class

To implement the TouristData class:

  1. Create the TouristData.java class file.

  2. Assign the class to the com.nokia.example.location.touristroute.model package.

    package com.nokia.example.location.touristroute.model;
  3. 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;
    
  4. 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;
  5. 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());
                }
            }
        }
  6. 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;
        }
  7. 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
            }
        }
  8. 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();
            }
        }
  9. 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();
            }
        }
  10. 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();
            }
        }
  11. 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.