×
Namespaces

Variants
Actions
Revision as of 17:49, 17 July 2012 by Oskar Bukolt (Talk | contribs)

Using a JSR-179 Location Provider and displaying the result on a Map

From Nokia Developer Wiki
Jump to: navigation, search

There are several pre-existing articles on how to initialize a LocationProvider to find out the location of a device. This article extends the concept by displaying the location on a map using two Nokia Mapping APIs the RESTful Map API and the Maps API for Java ME


MapImageLocation.png

Picture showing Maps generated from the Maps API for Java ME (left) and the RESTful Map API on an emulated device.

Article Metadata
Code ExampleTested withCompatibility
Device(s): All*
Dependencies: Maps API for Java ME v1.1
Platform Security
Capabilities: Location
Article
Keywords: Nokia Maps, Java ME, RESTful Map API, Static Maps, Maps API for Java ME
Created: jasfox (25 Nov 2011)
Last edited: Oskar Bukolt (17 Jul 2012)

Contents

Introduction

The following articles give an overview on how to create a LocationListener, and retrieve the longitude and latitude locating a device.

Most people do not think in terms of longitude and latitude, and would prefer to have their location displayed on a map. Fortunately Nokia provide access to their mapping services through a variety of public APIs. The article creates two examples combining the location listener with two of the Nokia mapping APIs, and discusses the merits of each approach.

  • A static map location using the RESTful Map API on Java ME
  • A pannable map location using Map Markers with the Maps API for Java ME

Location Provider Factory

It should be obvious that any application needing to discover the location of the device, will only require a single instance of a location provider. This can be ensured by the use of a factory pattern encapsulating a singleton. The class below offers two alternative methods to locate a device, either using GPS or CELL ID. The consuming application doesn't need to know the details, it merely needs to start and stop the location provider and offer the standard LocationListener interface to interact with the results.

Note Requests for location updates have been set up asynchronously at the default rate for the device, rather than querying directly with provider.getLocation(). This has been done for two reasons:

  • It allows the app to respond to other events (such as map panning or zoom) whilst waiting for a response.
  • It avoids setting up a thread to loop and continuously poll for locations and update a map; such a thread could easily overwhelm the rendering capabilities of a low level device.
public class LocationProviderFactory {
 
public static final int CELL_ID = 0;
public static final int GPS = 1;
private static LocationProvider provider;
private static String title;
private static final LocationProviderFactory INSTANCE =
new LocationProviderFactory();
 
/**
* I'm a singleton.
*/

private LocationProviderFactory() {
}
 
/**
* Start locating at a default interval, timeout and maxAge;
* @param listener - an listener who will receive location updates.
*/

public static void startLocating(LocationListener listener) {
if (provider != null) {
provider.setLocationListener(listener, -1, -1, -1);
}
}
 
/**
* Stop sending location updates to the currently registered listener
*/

public static void stopLocating() {
if (provider != null) {
provider.setLocationListener(null, -1, -1, -1);
}
}
 
/**
*
* @return The name of the current provider.
*/

public static String getTitle() {
return title;
}
 
/**
* Obtains a provider to be
* @param type
* @return The current location provider, or NULL if unable to set it up.
* @throws LocationException
*/

public static LocationProvider setProvider(int type) {
 
try {
if (type == CELL_ID) {
provider = INSTANCE.getCellIdProviderInstance();
title = "Obtaining Cell Id...";
} else if (type == GPS) {
provider = INSTANCE.getGPSProviderInstance();
title = "Obtaining GPS...";
}
 
} catch (LocationException lex) {
provider = null;
title = "Provider not found...";
} catch (NoClassDefFoundError ncdfex) {
provider = null;
title = "Provider not supported...";
}
 
return provider;
}
 
/**
*
* @return A Cell Id Location Provider
* @throws LocationException if the provider could not be found.
*/

private LocationProvider getCellIdProviderInstance() throws LocationException {
int[] methods = {Location.MTA_ASSISTED | Location.MTE_CELLID | Location.MTY_NETWORKBASED};
return LocationUtil.getLocationProvider(methods, null);
}
 
/**
*
* @return A GPS Location Provider
* @throws LocationException if the provider could not be found.
*/

private LocationProvider getGPSProviderInstance() throws LocationException {
Criteria criteria = new Criteria();
 
criteria.setCostAllowed(true);
criteria.setPreferredPowerConsumption(Criteria.NO_REQUIREMENT);
criteria.setSpeedAndCourseRequired(false);
criteria.setAltitudeRequired(false);
criteria.setAddressInfoRequired(false);
return LocationProvider.getInstance(criteria);
}
}

Displaying A Map Image based on a Location

The form below consists of three command buttons to select a location provider and start locating. A static map can be retrieved by making an http request to the RESTful Map API. The details of how to do this can be found in the Using_Map_Image_API_with_Java_ME article. Once an image is received, it is appended as an ImageItem to the Form

Note - the app id and token will need to be replaced with your own app id and token for the example to work correctly.

public class LocatorMapImageForm extends Form implements CommandListener, LocationListener {
 
// For the map
private LocateMapImageMIDlet midlet;
private QualifiedCoordinates lastKnownCoords;
private static final float THRESHOLD_DISTANCE = 100f;
// For the exit command, and the choice of location provider.
private static final Command EXIT_COMMAND = new Command("Exit", Command.EXIT, 1);
private static final Command LOCATE_BY_GPS_COMMAND = new Command("GPS", Command.SCREEN, 2);
private static final Command LOCATE_BY_CELL_ID_COMMAND = new Command("Cell", Command.SCREEN, 3);
private final MapImage mapImage;
private int count = 0;
 
/**
* Set Up, initialise the Static Map and display it on the screen.
* @param midlet
*/
public LocatorMapImageForm(LocateMapImageMIDlet midlet) {
super("Where Am I?");
 
 
this.midlet = midlet;
 
addCommand(EXIT_COMMAND);
addCommand(LOCATE_BY_GPS_COMMAND);
addCommand(LOCATE_BY_CELL_ID_COMMAND);
 
 
lastKnownCoords = new QualifiedCoordinates(0d, 0d, 0f, 0f, 0f);
 
// Ensure that we can request URLs from the RESTful Map URL.
mapImage = new MapImage(this.getHeight(), this.getWidth(),
// Start the application centered over the Royal Observatory in London.
51.4813491d, -0.0031516d, 15);
// You must get your own app_id and token by registering at
// https://api.developer.nokia.com/ovi-api/ui/registration
// Insert your own AppId and Token, as obtained from the above
// URL into the two methods below.
mapImage.setAppID("Your App ID goes here ...");
mapImage.setToken("Your Token goes here ...");
// Set language and image quality
mapImage.setMapLabelLanguage(MapLanguage.ENGLISH);
mapImage.setImageQuality(50);
 
// Set up a default initial location.
this.append(mapImage.getImage());
 
}
 
/**
* Respond to Command Events, there are three.
* <ul>
* <li>Locate by GPS</li>
* <li>Locate by Cell ID Provider</li>
* <ll>Exit App</li>
* </ul>
* @param command
* @param displayable
*/
public void commandAction(Command command, Displayable displayable) {
if (displayable == this) {
if (command == EXIT_COMMAND) {
midlet.exitMIDlet();
} else {
LocationProviderFactory.stopLocating();
if (command == LOCATE_BY_GPS_COMMAND) {
LocationProviderFactory.setProvider(LocationProviderFactory.GPS);
 
} else if (command == LOCATE_BY_CELL_ID_COMMAND) {
LocationProviderFactory.setProvider(LocationProviderFactory.CELL_ID);
 
}
setTitle(LocationProviderFactory.getTitle());
LocationProviderFactory.startLocating(this);
}
}
}


Filtering Location Updated events

The most important method is the implementation of locationUpdated(), which is fired whenever a location request response is received. Since the app is requesting location updates at a default rate, these will be received regardless of whether the location has changed. Rendering a map will involve generating network traffic, so in order not to request the same or a very similar location each time, the previous location is checked, and a new map only requested if the location has changed.

Note that providerStateChanged() must also be implemented as part of LocationListener, but is not used.

    /**
* Standard Event Listener for all JSR-179 implementations.
* Just get hold of the location and <b>if it has changed</b>
* significantly update the map.
* @param provider
* @param location
*/
public void locationUpdated(LocationProvider provider, Location location) {
 
// When the location is updated, request a Map
QualifiedCoordinates coords = location.getQualifiedCoordinates();
if (coords.distance(lastKnownCoords) > THRESHOLD_DISTANCE) {
updateMap(coords);
lastKnownCoords = new QualifiedCoordinates(
coords.getLatitude(), coords.getLongitude(), coords.getAltitude(),
coords.getHorizontalAccuracy(), coords.getVerticalAccuracy());
} else {
updateTitleOnly();
}
}
 
 
/**
* Standard Event Listener for all JSR-179 implementations.
*
* @param provider
* @param newState
*/
public void providerStateChanged(LocationProvider provider, int newState) {
// We don't need to do anything here, but could use this to switch
// to a backup provider if our current provicder is no longer
// available.
}


Visual Feedback when location is updated

In order to show that the location provider is working, some sort of visual feedback is required. In the case that the device has moved, this is simply updating the map. An additional method has been added to update the current title if a location request response has occurred and the map has not been updated. This may not be necessary in a real-life example.

    /**
* If the location has not moved outside of the Threshhold, do not update
* the map. Just show that we've done something by altering the map title.
*/
private void updateTitleOnly() {
 
if (count < 5) {
count++;
setTitle("." + getTitle());
} else {
setTitle(getTitle().substring(5));
count = 0;
}
}
 
/**
* Request a new Image and display Item on Form
* @param coords
*/
private synchronized void updateMap(QualifiedCoordinates coords) {
mapImage.setLatitude(coords.getLatitude());
mapImage.setLongitude(coords.getLongitude());
this.deleteAll();
this.append(mapImage.getImage());
setTitle(coords.getLatitude() + " " + coords.getLongitude());
}


Displaying a Map Location with Maps API for Java

The set up for this example is similar to the RESTful Map example, except that instead of using a Form, we use a MapCanvas. The MapCanvas class is part of the binary JAR of the API itself and can be downloaded from the Maps API for Java ME home page.

The MapCanvas needs the same three Command buttons to select a location provider and start locating. A map will be displayed as soon as the MapCanvas is initialized. The start location and zoom level have been set programmatically, and a StandardMapMarker added to highlight the current location.

Note - the app id and token will need to be replaced with your own app id and token for the example to work correctly.

/**
* A MapCanvas which displays a pannable map using Nokia Maps API for Java ME, the map displays the
* location of the device as retrieved using JSR 179.
*/

public class LocatorMapCanvas extends MapCanvas implements CommandListener, LocationListener {
 
// For the map
private LocateOnAMapMIDlet midlet;
private QualifiedCoordinates lastKnownCoords;
private static final float THRESHOLD_DISTANCE = 100f;
// For the exit command, and the choice of location provider.
private static final Command EXIT_COMMAND = new Command("Exit", Command.EXIT, 1);
private static final Command LOCATE_BY_GPS_COMMAND = new Command("GPS", Command.SCREEN, 2);
private static final Command LOCATE_BY_CELL_ID_COMMAND = new Command("Cell", Command.SCREEN, 3);
private MapStandardMarker marker;
 
private int count = 0;
 
public LocatorMapCanvas(Display display, LocateOnAMapMIDlet midlet) {
super(display);
addCommand(EXIT_COMMAND);
addCommand(LOCATE_BY_GPS_COMMAND);
addCommand(LOCATE_BY_CELL_ID_COMMAND);
 
this.midlet = midlet;
lastKnownCoords = new QualifiedCoordinates(0d, 0d, 0f, 0f, 0f);
 
map.setZoomLevel(15, 0, 0);
 
// Start the application centered over the Royal Observatory in London.
map.setCenter(new GeoCoordinate(51.4813491d, -0.0031516d, 0));
 
marker = mapFactory.createStandardMarker(map.getCenter()l);
map.addMapObject(marker);
 
}
 
public void onMapUpdateError(String description, Throwable detail, boolean critical) {
}
 
public void onMapContentComplete() {
}


Filtering Location Updated events

The implementations of locationUpdated() and providerStateChanged() are exact duplicates of the previous example for the reasons given above, the code is not duplicated below. Ideally, a new map should not be requested unless the position has changed. The Maps API for Java ME is more intelligent than the code using RESTful Map API web-service, since it is able to cache underlying map tile requests and will only request new Map tiles if necessary. However limiting the MapCanvas updates is still good practice to avoid the screen flickering on each location request response.

Visual Feedback when location is updated

The updateTitleOnly() method is an exact duplicate of the RESTful Map example and is purely to show some change each time the location is requested. The code has not been duplicated. The updateMap() method re-centers the MapDisplay and moves the marker. Either one of these operations will cause the MapCanvas to repaint automatically

    /**
* Re-center the map and move the marker.
* @param coords
*/

private synchronized void updateMap(QualifiedCoordinates coords) {
// When the location is updated, request a Map
map.setCenter(new GeoCoordinate(coords.getLatitude(),
coords.getLongitude(), coords.getAltitude()));
 
marker.setCoordinate(new GeoCoordinate(coords.getLatitude(),
coords.getLongitude(), coords.getAltitude()));
 
setTitle(coords.getLatitude() + " " + coords.getLongitude());
 
}

Which Mapping Service should be used?

Both examples work, and are able to display a map on the screen. The choice of which method to use is down to the developer, but the main consideration should be the end user.

The RESTful Map API example sends a single http request, and retrieves a single image in response. The size of the image received will depend on the image quality parameter, but at the standard 85% level of compression the image will be as follows:

RESTful Map API

Greenwich85.jpg|| This Map is 12.4Kb

The Maps API for Java ME example sends multiple http requests each receiving a small map tile, and then stitches the final image out of the relevant parts of the responses. Each tile will be about 4Kb, and for the first map displayed, typically nine map tiles will be required .

Maps API for Java ME

Greenwich tile1.png ... plus ... Greenwich tile2.png ... plus ... Greenwich tile3.png etc... || Each tile is about 4Kb , typically nine tiles = total 36 Kb

Therefore if a single static Map is required, using the RESTful Map API will probably involve less traffic. However, the Maps API for Java ME is able to cache the map tiles received whereas the RESTful Map API implementation is unable to re-use the map images requested, so it must request new images every time. Hence, if a series of maps are required, the Maps API for Java ME is much more economical in the medium term. This results in maps which are refreshed more quickly, and there is no traffic overhead.

When using a LocationProvider as in the example above, the Map is likely to change as the device moves, therefore, the preferred implementation would be to use the Maps API for Java ME. A saving in network traffic should occur after three maps have been displayed.

Summary

The RESTful Map API may provide a simpler alternative to the Maps API for Java ME for simple static mapping use cases and provides a smaller app size to download. However if the map needs to be refreshed at all, the tiling and caching capabilities of the Maps API for Java ME result in less network traffic for the user, and this benefit swiftly outweighs the benefits of a smaller download to the end user.

In the case of tracking the location of a device, it should be self evident, that the Maps API for Java ME approach should be preferred.

227 page views in the last 30 days.