×
Namespaces

Variants
Actions
Revision as of 08:03, 24 April 2013 by hamishwillee (Talk | contribs)

Using the RESTful Map API with Java ME

From Nokia Developer Wiki
Jump to: navigation, search
Featured Article
27 Nov
2011

This article explains how to create lightweight example application which uses the RESTful Map API to retrieve images and displays a pannable, zoomable map.

Warning.pngWarning: This example has been written purely as a demonstration of how to generate URLs to use the RESTful Map API, in reality, if you want to do anything more than just display a single map image (for example if you need to adding panning to a real life app), you will be better off using an integrated "dynamic" mapping Java ME library such as the Map API for Java ME at the cost of including an extra library jar file (approx 151 KB) as part of your app download. There is a significant reduction in required network traffic within the first couple of 200x200 pixel images due to the use of map tiling and caching.
The RESTful Map API may also be used within a location-based Web Widget as described here

Article Metadata
Code ExampleTested withCompatibility
Dependencies: Map API for Java ME
Article
Keywords: Java ME, RESTful Map API, Static Maps
Created: jasfox (21 Nov 2011)
Last edited: hamishwillee (24 Apr 2013)

Contents

Introduction

The RESTful Map API is a RESTful service designed to retrieve static map images based on a simple http request. As such it can be integrated into a Java ME midlet to create a very lightweight mapping application. The code example below displays a pannable, zoomable map.

MapImageExample.png

MapImageMidlet

A simple Midlet with the usual startApp(), pauseApp() and destroyApp() functions, nothing particularly special to see here, just wiring up a Canvas object so we can respond to events and displaying a Map on a screen.


public class MapImageMidlet extends MIDlet {    
/**
* Start up the application and initialize the Canvas
* so we can respond to events.
*/

protected void startApp() {
Display display = Display.getDisplay(this);
MapImageCanvas canvas = new MapImageCanvas(this);
// Ensure that we can respond to events.
canvas.setCommandListener(canvas);
display.setCurrent(canvas);
}
/**
* Pause the app - nothing to do.
*/

protected void pauseApp() {
}
/*
* Clean up goes here. Nothing to do.
*/

protected void destroyApp(boolean unconditional) {
}
/**
*
*/

public void exitMIDlet() {
destroyApp(true);
notifyDestroyed();
}
}


MapImageCanvas

The MapImageCanvas component responds to key events and pans and zooms the map accordingly. The initialization of the object adds a close button and sets up the MapImage class for an arbitrary location - in this case the Royal Observatory in London, which is used to define the Prime Meridian.

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 MapImageCanvas extends Canvas implements CommandListener {
private Command cmExit; // Exit midlet
private MapImageMidlet midlet;
private Image im = null;
private final PannableMapImage mapImage;
 
public MapImageCanvas(MapImageMidlet midlet) {
this.midlet = midlet;
// Create exit command
cmExit = new Command("Exit", Command.EXIT, 1);
addCommand(cmExit);
// Ensure that we can request URLs from the RESTful Map API URL.
mapImage = new PannableMapImage(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);
// Don't show addresses.
mapImage.setRevGeo(-1);
getImage();
}

The getImage() method requests a jpeg using the RESTful Map API with an http request. The important point here is to make sure that all connections are closed regardless of the success or failure of the request. Failure to do this basic housekeeping results in the connection failing after a few requests with a "No Response Entries Available" error. Once an image is received, it is held as a static image in the im Object.

    /**     
* This is the main function of the App. It requests a URL of the form:
* http://m.nokia.me/?h=200&w=300&z=15&i=1&c=40,30
* and holds the result as an image to be displayed by the paint function.
*
* Full details of the available parameters can be found at
* www.developer.nokia.com/Develop/Maps/Map_Image_API/
*
*
* Best to make this synchronized to avoid flooding the app with requests
* When the panning/zooming buttons are repeatedly pressed.
*/

private synchronized void getImage() {
ContentConnection c = null;
DataInputStream dis = null;
try {
try {
c = (ContentConnection) Connector.open(mapImage.getURL());
int len = (int) c.getLength();
dis = c.openDataInputStream();
if (len > 0) {
byte[] data = new byte[len];
dis.readFully(data);
im = Image.createImage(data, 0, data.length);
}
} catch (IOException ioe) {
// Failed to read the url. Can't do anything about it, just don't
// update the image.
} finally {
// Regardless of whether we are successful, we need to close
// Connections behind us. Basic Housekeeping.
if (dis != null) {
dis.close();
}
if (c != null) {
c.close();
}
}
} catch (IOException ioe) {
// closure of connections may fail, nothing we can do about it.
}
}

Simple panning and zooming control using the keyPressed() method, all the functionality of deciding how to move has been encapsulated in the PannableMapImage class itself.


    /**
* Control to allow the panning and zooming of the Map.
* @param keyCode
*/

protected void keyPressed(int keyCode) {
int gameAction = getGameAction(keyCode);
switch (gameAction) { case UP:
mapImage.moveNorth();
break;
case DOWN:
mapImage.moveSouth();
break;
case LEFT:
mapImage.moveWest();
break;
case RIGHT:
mapImage.moveEast();
break;
case FIRE:
mapImage.recenterMap();
break;
// Star key zooms in.
case GAME_C:
mapImage.increaseZoom();
break;
// Hash Key zooms out.
case GAME_D:
mapImage.decreaseZoom();
break;
}
// Ensure that the latest image is uploaded.
getImage();
// Then ensure that the map gets repainted.
repaint();
}

Whenever paint() is called, the latest stored Map Image is displayed on screen.

    /**
* Draw immutable image
*/

protected void paint(Graphics g) {
if (im != null) {
g.drawImage(im, 10, 10, Graphics.LEFT | Graphics.TOP);
}
}
 
/**
* Close the application if the Exit button is pressed.
* @param c
* @param d
*/

public void commandAction(Command c, Displayable d) {
if (c == cmExit) {
midlet.exitMIDlet();
}
}

Static Map Image

The Static Map Image is a value object used to hold the current position on the globe. It is able to calculate the required URL to request an appropriate Nokia Map from the RESTful Map API.

The URLs requested will be of the form

  http://m.nokia.me/?appId=XXX&token=YYY&h=200&w=300&z=15&i=1&c=40,30

For initialization, a location on the globe is required along with the current zoom and the size of map to display. All of these apart from the map size can then be altered

public class StaticMapImage {
// A few useful constants to avoid falling of the edge of the globe.
private static final int NORTH_POLE = 90;
private static final int SOUTH_POLE = -90;
private static final int INTERNATIONAL_DATE_LINE = 180;
private static final int MAXIMUM_ZOOM = 20;
private static final int MINIMUM_ZOOM = 1;
// Height and width of the images to retreive are fixed.
private final int mapHeight;
private final int mapWidth;
// Defines the position on the globe and required zoom level.
private double lng;
private double lat;
private int zoom;
private String appId;
private String token;
/** Which Map language to use
* @see MapLanguage*/

private String mapLabelLanguage = MapLanguage.ENGLISH;
/** Which map type to display
* * @see MapType*/

private int mapType = MapType.NORMAL_MAP;
/** Whether to apply image compression - default is 85% */
private int imageQuality = -1;
/** Whether or not to display an address on the map */
private int revGeo = -1;
/**
* Constructor - defines the size of the maps to request.
* @param mapHeight - the height of the map.
* @param mapWidth - the width of the map.
* @param latitiude - initial latitude.
* @param longitude - initial longitude.
* @param zoom - initial zoom level.
*/

public MapImage(int mapHeight, int mapWidth, double latitiude,
double longitude, int zoom) {
this.mapHeight = mapHeight;
this.mapWidth = mapWidth;
this.lat = latitiude;
this.lng = longitude;
this.zoom = zoom;
}

The app id and token help identify the application that has been registered for the app. You must get your own app_id and token by registering at https://api.developer.nokia.com/ovi-api/ui/registration


    public void setAppID(String appId) {
this.appId = appId;
}
public void setToken(String token) {
this.token = token;
}

The basic URL is built up from the:

  • app id
  • token
  • image height
  • image width
  • longitude and latitude
  • zoom

Other parameters could be added as desired, see www.developer.nokia.com/Develop/Maps/Map_Image_API/ for details.

A couple of additional example parameters have been added for good measure.

  • Map Type - SATELLITE, TRANSPORT etc.
  • Map Label Language - currently ranging from ENGLISH to RUSSIAN to CHINESE.
  • Reverse Geo Coding
  • Image quality


Image Quality vs File Size

The most important of these is the image quality parameter. This simple application does not cache or tile the images it requests, therefore each request involves a round trip to the RESTful Map API web service. Such a situation is not ideal, but a reasonable compromise when trying to display a map on a device without sufficient processing capability for a better solution. Each the details on each map.

Compression Image Comments
No compression used Greenwich100.jpg This map is 36.7Kb, - very large, but very sharp
Standard 85% compression used. Greenwich85.jpg This Map is 12.4Kb - still a reasonable sharpness at a quarter of the previous size
20% compression used Greenwich20.jpg This Map is 4.2Kb - the texts are less readable at an eighth of the original size. However this could be

a reasonable compromise for a small screen display.


/**
*
* @return a string of the form:
* http://m.nok.it/?appId=XXX&token=YYY&h=200&w=300&z=15&i=1&c=40,30
* replacing the values with the height and width of the image, the zoom level and
* the longitude and latitude.
*/

public String getURL() {
//creating the url
String url = "http://m.nok.it/?"
+ "app_id=" + appId + "&token=" + token
+ "&h=" + mapHeight + "&w=" + mapWidth + "&q=70"
+ "&z=" + getZoom() + "&c=" + getLatitude() + "," + getLongitude();
 
if (MapLanguage.ENGLISH.equals(mapLabelLanguage) == false) {
url = url + "&mv=" + mapLabelLanguage;
}
if (mapType != MapType.NORMAL_MAP) {
url = url + "&t=" + mapType;
}
if (revGeo > -1) {
url = url + "&i=" + revGeo;
}
 
if (imageQuality > -1) {
url = url + "&q=" + imageQuality;
}
 
return url;
}

The remainder of the class consists of the usual getters and setters, with sanity checks to avoid placing the location in an impossible position

    /**
* @return the current Latitude
*/

public double getLongitude() {
return lng;
}
 
/**
* @return the current Latitude
*/

public void setLongitude(double lng) {
 
if (lng > INTERNATIONAL_DATE_LINE) {
lng = lng - 360;
} else if (lng < -INTERNATIONAL_DATE_LINE) {
lng = lng + 360;
}
this.lng = lng;
}
 
/**
* @return the current Longitude
*/

public double getLatitude() {
return lat;
}
 
/**
* @return the current Longitude
*/

public void setLatitude(double lat) {
if (lat > NORTH_POLE) {
lat = NORTH_POLE;
} else if (lat < SOUTH_POLE) {
lat = SOUTH_POLE;
}
this.lat = lat;
}
 
/**
* @return the zoom
*/

public int getZoom() {
return zoom;
}
 
/**
* @return the zoom
*/

public void setZoom(int zoom) {
if (zoom < MINIMUM_ZOOM) {
zoom = MINIMUM_ZOOM;
} else if (zoom > MAXIMUM_ZOOM) {
zoom = MAXIMUM_ZOOM;
}
this.zoom = zoom;
}
 
/**
* @return the mapLabelLanguage
* @see MapLanguage
*/

public String getMapLabelLanguage() {
return mapLabelLanguage;
}
 
/**
* @param mapLabelLanguage the mapLabelLanguage to set
*
* Current languages are ENGLISH, CHINESE,GERMAN,FRENCH,ITALIAN, SPANISH, RUSSIAN
* Others e.g. ARABIC will be rolled out soon.
* @see MapLanguage
*/

public void setMapLabelLanguage(String mapLabelLanguage) {
this.mapLabelLanguage = mapLabelLanguage;
}
 
/**
* @return the imageQuality
*/

public int getImageQuality() {
return imageQuality;
}
 
/**
* @param imageQuality the imageQuality to set
*/

public void setImageQuality(int imageQuality) {
this.imageQuality = imageQuality;
}
 
/**
* @return the revGeo
*/

public int getRevGeo() {
return revGeo;
}
 
/**
* Whether and where to display address info on the map.
* <ul>
* <li> i=0 The address text box is shown on top center part of the image.</li>
* <li>i=1
* The address text box is shown above the position marker dot.</li>
* <li>i=2
* The address text box is shown on the left side of the position marker dot.</li>
* <li>i=3
* The address text box is shown on the right side of the position marker dot.</li>
* <li>i=4
* The address text box is shown below the position marker dot.</li>
*
* <li>i=-1 will switch off. </li>
* </ul>
* @param revGeo Reverse Geocoding positional information.
*/

public void setRevGeo(int revGeo) {
this.revGeo = revGeo;
}
 
/**
* the current mapType to display.
*
* @see MapType
*/

public int getMapType() {
return mapType;
}
 
/**
* @param mapType the mapType to set.
* @see MapType
*/

public void setMapType(int mapType) {
this.mapType = mapType;
}

Pannable Map Image

Rather than allowing the client code to set the longitude and latitude directly, the move North/South/East/West methods are used, so that the increment in each direction is determined directly by the current zoom level.

/**
* Provides a pannable, zoomable version of the static Map Image provider.
*/

public class PannableMapImage extends StaticMapImage {
 
// Defines the position of the dot the globe and required zoom level.
private double dotLng;
private double dotLat;
 
// Since Java ME doesn't have a pow function, it make sense to define how
// far (in degrees) each increment should be - this works well at lower
//latitudes. Each value is approximately half the previous one.
private static final double[] LAT_LONG_INCREMENT = {
64d, 32d, 16d, 8d, 4d, 2d, 1d, 0.5d, 0.25d, 0.0625d, 0.032d, 0.016d,
0.008d, 0.004d, 0.002d, 0.001d, 0.0005d, 0.00025d,
0.000125d, 0.0000625d, 0.00003125d};
 
 
public PannableMapImage(int mapHeight, int mapWidth, double latitiude,
double longitude, int zoom) {
super(mapHeight, mapWidth, latitiude, longitude, zoom);
dotLng = getLongitude();
dotLat = getLatitude();
 
}
 
public PannableMapImage(int mapHeight, int mapWidth) {
this(mapHeight, mapWidth, 0, 0, 10);
}
 
public String getURL() {
//creating the url
return super.getURL() + "&ctr=" + dotLat + "," + dotLng;
}
 
/**
* Increase the current longitude, taking care when crossing the
* International Date line.
*/

public void moveEast() {
setLongitude(getLongitude() + LAT_LONG_INCREMENT[getZoom() - 1]);
 
}
 
/**
* Decrease the current longitude, taking care when crossing the
* International Date line.
*/

public void moveWest() {
setLongitude(getLongitude() - LAT_LONG_INCREMENT[getZoom() - 1]);
 
}
 
/**
* Increase the current latitude, taking care when reaching the
* North Pole
*/

public void moveNorth() {
 
setLatitude(getLatitude() + LAT_LONG_INCREMENT[getZoom() - 1]);
}
 
/**
* Increase the current latitude, taking care when reaching the
* North Pole
*/

public void moveSouth() {
setLatitude(getLatitude() - LAT_LONG_INCREMENT[getZoom() - 1]);
 
}
 
/**
* zoom out by one level.
*/

public void decreaseZoom() {
setZoom(getZoom() - 1);
 
}
 
/**
* zoom in by one level.
*/

public void increaseZoom() {
setZoom(getZoom() + 1);
 
}
 
public void recenterMap(){
dotLng = getLongitude();
dotLat = getLatitude();
}
}

Comparision of Network traffic generated by the RESTful Map API and Maps API for Java ME

Nokia offers several public APIs which are able to display a Map and can be integrated into a Java ME app. Both RESTful Map API and Map API for Java ME are able to display a map on the screen. The choice of which API to use is down to the developer, but the main consideration should be the end user experience.

Typically, 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, the RESTful Map API will probably involve less traffic. However, the Maps API for Java ME is able to cache the maps tiles received whereas the RESTful Map API implementation is unable 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 a lower traffic overhead.

With a pannable map, the Map Images will need to be refreshed each time the viewpoint moves, therefore, the preferred implementation would be to use the Maps API for Java ME if possible, as a saving in network traffic should occur after three maps have been displayed. Even if a smaller static refreshable map is required, the network traffic would be reduced by using a Custom Map Item which takes advantage of map caching.

Summary

The RESTful Map API may provide a simpler alternative to the Map API for Java ME for some simple Java ME static mapping use cases. Note that in the case of a pannable map, the same application can be written with the following code using the Map API for Java ME

 ApplicationContext.getInstance().setAppID(...);
ApplicationContext.getInstance().setToken(...);
 
Display display = Display.getDisplay(this);
MapCanvas mapCanvas = new MapCanvas(display);
display.setCurrent(mapCanvas);
323 page views in the last 30 days.
×