Using the RESTful Map API with Java ME
This article explains how to create lightweight example application which uses the RESTful Map API to retrieve images and displays a pannable, zoomable map.
The RESTful Map API may also be used within a location-based Web Widget as described here
Article Metadata
Code Example
Tested with
Compatibility
Article
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.
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
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.
/**
*
* @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
|
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
... plus ... ... plus ... 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);








