A series of code examples are available as part of the download for the Maps API for Java ME. These examples cover most common use cases of the API and range in difficulty from a simple display of a map up to a full blown web service client. Step-by-step instructions and an in-depth commentary on a selection of the code examples is available below.
The code examples are freely available for further modification and use, and may be used as the basis for building your own applications using the Maps API for Java ME.
The following code example provides basic navigation and displays a map using Nokia Maps for Java ME. This MIDlet displays
a map and allows the user to move it around and change the map zoom level and also contains a command button to allow the
User to exit.
1. Create a MIDlet Template with standard startApp(), destroyApp() and pauseApp() methods.
public class MapMIDlet extends MIDlet {
protected void startApp() throws MIDletStateChangeException {
}
protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
}
protected void pauseApp() {
}
}
2. Update the existing ApplicationContext with your credentials in the startApp() method in order to register the application when retrieving Nokia Maps over the internet.
Note: A Singleton already exists that you can use to hold the details of the an ApplicationContext
public class MapMIDlet extends MIDlet {
protected void startApp() throws MIDletStateChangeException {
ApplicationContext.getInstance().setAppID(...);
ApplicationContext.getInstance().setToken(...);
}
}
3. Maps are displayed on a MapCanvas object which requires an instance of the device's Display. Therefore you must get the display, create the mapCanvas and make it into the current display. Add all the code that can be added to the startApp() method.
public class MapMIDlet extends MIDlet {
protected void startApp() throws MIDletStateChangeException {
ApplicationContext.getInstance().setAppID(...);
ApplicationContext.getInstance().setToken(...);
Display display = Display.getDisplay(this);
MapCanvas mapCanvas = new MapCanvas(display){
public void onMapUpdateError(String description, Throwable detail, boolean critical) {
}
public void onMapContentComplete() {
}
};
display.setCurrent(mapCanvas);
}
}
Note: The three lines of code you have just added creates a working application. It will display a map that can be navigated and zoomed. There is only one problem, there are no controls to close or allow anything except default interaction with the application.
4. Add an EXIT button that notifies the MIDlet to destroy itself, so you can get a closable application. Common code to close and interact with the map is likely to be re-used, so it should be broken out into a separate base class that extends the MapCanvas.
The Base class should implement CommandListener. It offers a standard interface for allowing interactions with button presses.
public class Base extends MapCanvas implements CommandListener {
private final Command EXIT = new Command("Exit", Command.EXIT, 1);
protected MIDlet midlet; // for notifyDestroyed
public Base(Display display, MIDlet midlet) {
super(display);
this.midlet = midlet;
addCommand(EXIT);
setCommandListener(this);
}
public void commandAction(final Command c, Displayable d) {
if (c == EXIT) {
midlet.notifyDestroyed();
}
}
public void onMapUpdateError(String description, Throwable detail, boolean critical) {
}
public void onMapContentComplete() {
}
}
5. Modify the MapMIDlet class to use a sub class of base and to tidy up the implementation.
public class MapMIDlet extends MIDlet {
protected void startApp() throws MIDletStateChangeException {
ApplicationContext.getInstance().setAppID(...);
ApplicationContext.getInstance().setToken(...);
Display display = Display.getDisplay(this);
MapDemo minimalMap = new MapDemo(display, this);
display.setCurrent(minimalMap);
}
private class MapDemo extends Base {
public MapDemo(Display display, MIDlet midlet) {
super(display, midlet);
}
}
}
By default, the map displayed on a MapCanvas starts at maximum zoom and will be centered over the Gulf of Guinea (0° longitude, 0 ° latitude). Basic map navigation
and zoom are provided out of the box and no additional code is required in order to implement this. It is possible to move
the Map (by pressing the keys 2, 4 ,6 and 8) and zoom the map using the * and # keys. The touch screen will also be available
to move the pointer for appropriate Devices.
To accomplish this the MapCanvas already responds to standard Canvas events, then calls MapDisplay.setCenter() and MapDisplay.setZoomLevel() in order to move and zoom the map. Alternatively, both of these can be combined in one call using a MapDisplayState
1) If you want to set your own zoom and location, alter the startApp() method:
public class MapMIDlet extends MIDlet {
protected void startApp() throws MIDletStateChangeException {
ApplicationContext.getInstance().setAppID(...);
ApplicationContext.getInstance().setToken(...);
Display display = Display.getDisplay(this);
MapDemo minimalMap = new MapDemo(display, this);
// Set the Map to display Malmo, Sweden.
minimalMap.getMapDisplay().setCenter(new GeoCoordinate(55.583, 13.033,0));
minimalMap.getMapDisplay().setZoomLevel(15,0,0);
display.setCurrent(minimalMap);
}
private class MapDemo extends Base {
public MapDemo(Display display, MIDlet midlet) {
super(display, midlet);
}
}
}
The following coded example adds a marker to a map and allows the user to interact with it using Nokia Maps for Java ME.
1. Create a new MarkerMIDlet class based on the previous example and replace the private MapDemo class with a new public class as shown in the sample code below.
Note: The class will need to be public in order to interact with events discussed later.
public class MarkerMIDlet extends MIDlet {
protected void startApp() throws MIDletStateChangeException {
ApplicationContext.getInstance().setAppID(...);
ApplicationContext.getInstance().setToken(...);
Display display = Display.getDisplay(this);
MarkerDemo md = new MarkerDemo(display, this);
display.setCurrent(md);
}
protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
}
protected void pauseApp() {
}
}
public class MarkerDemo extends Base {
public MarkerDemo (Display display, MIDlet midlet) {
super(display, midlet);
}
}
Note: The sample code above has accomplished the following:
Commands and is able to close the application.
2. To add a Marker, create a StandardMarker using mapFactory. Add it to the displayed mapCanvas.
Note: Adding a Marker is simple because the mapCanvas offers a factory pattern to create objects.

public class MarkerDemo extends Base {
private MapStandardMarker marker;
public MarkerDemo (Display display, MIDlet midlet) {
super(display, midlet);
marker = mapFactory.createStandardMarker (new GeoCoordinate(55.583, 13.033,0));
map.addMapObject(marker);
}
}
Result: The sample code above will display a standardMarker at a fixed location.
3. To allow additional commands to run, you must modify the commandAction() method in the Base class such that it can pass on the processing of other commands to subcalsses as necessary. In addition to the EXIT command to
close the app, it is useful for all Mapping applications to be able to handle the loss of connection to the Nokia mapping
service. YES and NO commands have been added to the Base class to cope with this.
public class Base extends MapCanvas implements CommandListener {
public void commandAction(final Command c, Displayable d) {
if (c == EXIT) {
midlet.notifyDestroyed();
} else if (c == YES) {
map.reconnect();
display.setCurrent(current);
} else if (c == NO) {
display.setCurrent(current);
} else {
commandRun(c);
}
}
protected void commandRun(Command c) {
}
public void onMapUpdateError(String description, Throwable detail, boolean critical) {
reconnectAlert.setTimeout(Alert.FOREVER);
reconnectAlert.setString("Do you wish to reconnect?");
reconnectAlert.addCommand(NO);
reconnectAlert.addCommand(YES);
reconnectAlert.setCommandListener(this);
current = display.getCurrent();
display.setCurrent(reconnectAlert);
}
}
4. Add two commands to MarkerDemo and override commandRun() from the base class to implement adding and moving the marker.
Note: This step adds a standardMarker to the GeoCoordinate at the center of the screen, and then replaces the add marker command with a move marker command. The move command merely re-centers the existing standardMarker.
public class MarkerDemo extends Base {
private final Command SET_MARKER = new Command("Set marker", Command.OK, 1);
private final Command MOVE_MARKER = new Command("Move marker", Command.OK, 1);
private MapStandardMarker marker;
public MarkerDemo (Display display, MIDlet midlet) {
super(display, midlet);
addCommand(SET_MARKER);
}
protected void commandRun(Command c) {
if (c == SET_MARKER || c == MOVE_MARKER) {
if (marker == null) {
// change to move command
removeCommand(SET_MARKER);
addCommand(MOVE_MARKER);
}
selectPosition();
}
}
private GeoCoordinate selectPosition() {
Point center = new Point(map.getWidth() / 2, map.getHeight() / 2);
GeoCoordinate gc = map.pixelToGeo(center);
if (marker == null) {
// create new marker
marker = mapFactory.createStandardMarker(gc, 10, null, null);
map.addMapObject(marker);
} else {
// move existing marker
marker.setCoordinate(gc);
}
return gc;
}
}
Result: After this step, the standardMarker moves to a new position when the move marker command is selected.
5. Introduce a framework to be able to respond to mapCanvas events. The base class can already respond to command buttons because it implements commandListener, you must manually enable the handling of events from the mapCanvas itself, before you can make the marker draggable.
Note: In order to be informed of mapCanvas events, you need to register a mapComponent with the mapCanvas. The mapComponent must implement an EventListener which holds the code to respond to the event. In our case, our MapComponent will eventually provide a custom EventListener with code which then drags the StandardMarker across the map.

An implementation of mapComponent which offers a vanilla implementation of eventListener would be useful. You could then over-ride as required. You can add the vanilla implementation, which provides override hooks, to the Base class as shown.
public abstract class Base extends MapCanvas implements CommandListener {
…
public class MapComponentImpl implements MapComponent {
public EventListener getEventListener() {
return new EventListenerImpl();
}
public void attach(MapDisplay map) {
}
public void detach(MapDisplay map) {
}
public String getId() {
return "testcomponent";
}
public String getVersion() {
return "0.1";
}
public void mapUpdated(boolean zoomChanged) {
}
public void paint(Graphics g) {
}
}
protected class EventListenerImpl implements EventListener {
public boolean keyReleased(int keyCode, int gameAction) {
return false;
}
public boolean keyRepeated(int keyCode, int gameAction, int repeatCount) {
return false;
}
public boolean keyPressed(int keyCode, int gameAction) {
return false;
}
public boolean pointerDragged(int x, int y) {
return false;
}
public boolean pointerPressed(int x, int y) {
return false;
}
public boolean pointerReleased(int x, int y) {
return false;
}
}
}
Note: All the methods of the vanilla EventListenerImpl return false because the event has not been consumed. Similarly the attach(),detach()and paint() methods of the vanilla MapComponentImpl contain no code since they do nothing by themselves. This framework allows us to wire up a custom event listener as shown in the sample code below:
public class MarkerDemo extends Base {
…
public MarkerDemo(Display display, MIDlet midlet) {
super(display, midlet);
addCommand(SET_MARKER);
// Mover component handles pointer events to move the marker
map.addMapComponent(new Mover());
}
/**
* MapComponent to move the marker
*/
class Mover extends MapComponentImpl {
public EventListener getEventListener() {
return new MoverEventListener();
}
}
/**
* Handles pointer and key events to move the marker
*/
class MoverEventListener extends EventListenerImpl {
}
}
6. Add the code to the MoverEventListener to drag the standardMarker across the map.
Note: To drag a marker, consider the following three pointer events:
These three actions naturally map to the pointerDragged(), pointerPressed() and pointerReleased() events. In each case, if event has been successfully consumed, the method should return true. Similarly, if the display
needs to be updated, an additional call to
repaint() should be made, to inform the mapCanvas.
public class MarkerDemo extends Base {
…
class MoverEventListener extends EventListenerImpl {
public boolean pointerDragged(int x, int y) {
if (movingMarker) {
marker.setCoordinate(map.pixelToGeo(new Point(x, y)));
repaint();
return true;
} else {
return false;
}
}
public boolean pointerPressed(int x, int y) {
if (marker != null && map.getObjectAt(new Point(x, y)) == marker) {
marker.setColor(0xFF0000);
movingMarker = true;
repaint();
return true;
} else {
return false;
}
}
public boolean pointerReleased(int x, int y) {
if (movingMarker) {
marker.setCoordinate(map.pixelToGeo(new Point(x, y)));
marker.setColor(0x000000);
map.setCenter(marker.getCoordinate());
movingMarker = false;
repaint();
return true;
} else {
return false;
}
}
}
}
7. Finally add one more event handler to allow the 5 key to move the marker as well. The 5 key maps to Canvas.FIRE.
Note: The new method overrides keyReleased() and, when appropriate, calls the pre-existing command handler with a "set marker" command to avoid duplicating code. As
you can see in the sample code below, the method only returns true, if the event has been successfully consumed.
class MoverEventListener extends EventListenerImpl {
public boolean keyReleased(int keyCode, int gameAction) {
if (gameAction == Canvas.FIRE) {
commandAction(SET_MARKER, null);
return true;
} else {
return false;
}
}
… etc
Result: This completes the functionality required to create and drag a standardMarker across a mapCanvas.
The following coded example uses custom mapMarkers to display an image of your own choice on a mapCanvas rather than relying on the default standardMarkers to indicate points of interest.
1. Start from a minimal map that uses the base class. It displays a navigable, zoomable mapCanvas on the screen.
Note: The custom mapMarker will not be draggable, so you do not need to define the CustomMarkerDemo class as public since you will not be responding to mapCanvas events directly.
public class CustomMarkerMIDlet extends MIDlet {
protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
}
protected void pauseApp() {
}
protected void startApp() throws MIDletStateChangeException {
ApplicationContext.getInstance().setAppID(...);
ApplicationContext.getInstance().setToken(...);
Display display = Display.getDisplay( this );
CustomMarkerDemo cd = new CustomMarkerDemo(display, this );
display.setCurrent(cd);
}
private class CustomMarkerDemo extends Base{
public CustomMarkerDemo(Display display, MIDlet midlet) {
super(display, midlet);
}
}}
2. Add a new Add Marker Command, and respond to it when the command is selected. Start by displaying an ordinary standardMarker at fixed co-ordinates.
Note: The commandRun() method has already been defined in the Base class and should be overridden to display the marker. You can use the existing
factory method in the mapFactory to a createStandardMarker() for now, and add it to the mapCanvas.
public class CustomMarkerDemo extends Base{
private final Command ADD_MARKER = new Command("Add marker", Command.OK, 1);
public CustomMarkerDemo(Display display, MIDlet midlet) {
super(display, midlet);
addCommand(ADD_MARKER);
}
protected void commandRun(Command c) {
if (c == ADD_MARKER ) {
marker = mapFactory.createStandardMarker (new GeoCoordinate(55.583, 13.033,0));
map.addMapObject(marker);
}
}
}
}
3. Update the code to load an image into memory and display a custom mapMarker rather than using a standardMarker. This can
be done using the createMapMarker() method.
Note: In a similar manner to the previous example, mapFactory offers a method for the creating custom mapMarkers. The createMapMarker() method is similar to createStandardMarker() method, but it has an additional parameter to display a custom image. The coded
example includes this image as a resource in the examples jar, and thus must be loaded into the memory of the device using
the standard IO mechanism prior to display.
public class CustomMarkerDemo extends Base{
private final Command ADD_MARKER = new Command("Add marker", Command.OK, 1);
public CustomMarkerDemo(Display display, MIDlet midlet) {
super(display, midlet);
addCommand(ADD_MARKER);
}
protected void commandRun(Command c) {
if (c == ADD_MARKER ) {
Image markerIcon = null;
try {
markerIcon = Image.createImage("/marker.png");
} catch (IOException e) {
e.printStackTrace();
error("marker image not found");
return;
}
MapMarker mm = mapFactory.createMapMarker(new GeoCoordinate(55.583, 13.033,0),
markerIcon);
map.addMapObject(mm);
}
}
}
4. Alter the add marker command to display the custom mapMarker at the center of the map rather than a fixed position. Firstly,
derive the GeoCoordinate of the center of the screen, and then pass this into the createMapMarker() method.
Note: There is a useful helper function, pixelToGeo(), which converts screen pixels to a GeoCoordinate.
protected void commandRun(Command c) {
if (c == ADD_MARKER ) {
Image markerIcon = null;
try {
markerIcon = Image.createImage("/marker.png");
} catch (IOException e) {
e.printStackTrace();
error("marker image not found");
return;
}
Point center = new Point( map.getWidth() / 2,map.getHeight() / 2 );
GeoCoordinate gc = map.pixelToGeo(center);
MapMarker mm = mapFactory.createMapMarker(gc, markerIcon);
map.addMapObject(mm);
}
}
Note: Executing the sample code above will not display the custom mapMarker quite where one might expect. The location marked by the mapMarker is known as the anchor point and by default this is set to the top left-hand corner of the image. The actual point visually
marked by any image varies. Since the image resource in the example code is a doughnut shape, one' eye is drawn to the hole
in the center of the doughnut, not the top left corner. Hence mapMarker appears to be offset when anchored to the left corner.
You must alter the anchor point. The problem is illustrated below:

5. Fix the problem by defining a new anchor point for the mapMarker.
Do so before adding it to the mapCanvas for display so that the image will be appropriately displaced. To set the anchor point in the center of an image, you must
displace the anchor point upwards by half its height and leftwards by half its width and thus the custom image should appear
to display directly over the position clicked.
protected void commandRun(Command c) {
if (c == ADD_MARKER ) {
Image markerIcon = null;
try {
markerIcon = Image.createImage("/marker.png");
} catch (IOException e) {
e.printStackTrace();
error("marker image not found");
return;
}
Point center = new Point( map.getWidth() / 2,map.getHeight() / 2 );
GeoCoordinate gc = map.pixelToGeo(center);
MapMarker mm = mapFactory.createMapMarker(gc, markerIcon);
Point anchor = new Point( -markerIcon.getWidth()/2, -markerIcon.getHeight()/2 );
mm.setAnchor( anchor );
map.addMapObject(mm);
}
}
The following code example provides a simple routing application.
Note: Maps API for Java ME allows devices to access additional location-based services such as routing. A route is a path
between at least two Waypoints, the starting point and the destination, with optional intermediate waypoints in between. Using the Maps API, you can get
optimal routes that match your own calculation criteria but that are based on up-to-date maps and take into account real-time
traffic information.
1. Start from a minimal map that uses the base class as this will display a navigable, zoomable mapCanvas on the screen. As well, define several Commands and a skeleton commandRun () method where code will be added later in order to respond to these Commands.
public class RoutingMIDlet extends MIDlet {
protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
}
protected void pauseApp() {
}
protected void startApp() throws MIDletStateChangeException {
ApplicationContext.getInstance().setAppID(...);
ApplicationContext.getInstance().setToken(...);
Display display = Display.getDisplay( this );
RoutingDemo d = new RoutingDemo(display, this );
display.setCurrent(d);
}
}
public class RoutingDemo extends Base {
private final Command ADD = new Command("Add waypoint", Command.OK, 3);
private final Command CALCULATE = new Command("Calculate route",
Command.OK, 3);
private final Command CALCULATE_WITH_MODE = new Command(
"Calculate bicycle route ", Command.OK, 3);
private final Command RESET = new Command("Reset", Command.OK, 3);
public RoutingDemo(Display display, MIDlet midlet) {
super(display, midlet);
addCommand(ADD);
}
protected void commandRun(Command c) {
}
}
2. Add the capability to add waypoints to a list.
Note: Waypoints are an ordered collection of GeoCordinates. A WaypointParameterList class for holding the GeoCordinates already exists. You can add GeoCoordinates by using the addCoordinate() method. Ideally the user should receive visual feedback, so whenever you add a Waypoint you should also consider adding a
standardMarker to the mapCanvas.
public class RoutingDemo extends Base {
private final Command ADD = new Command("Add waypoint", Command.OK, 3);
private final Command CALCULATE = new Command("Calculate route",
Command.OK, 3);
private final Command CALCULATE_WITH_MODE = new Command(
"Calculate bicycle route ", Command.OK, 3);
private final Command RESET = new Command("Reset", Command.OK, 3);
private WaypointParameterList wpl = new WaypointParameterList();
public RoutingDemo(Display display, MIDlet midlet) {
super(display, midlet);
addCommand(ADD);
}
private void addWaypoint() {
Point center = new Point(map.getWidth() / 2, map.getHeight() / 2);
GeoCoordinate gc = map.pixelToGeo(center);
wpl.addCoordinate(gc);
map.addMapObject(mapFactory.createStandardMarker(gc, 10, null, null));
}
protected void commandRun(Command c) {
if (c == ADD) {
addWaypoint();
}
}
}
3. Add the capability to clear the list of waypoints noting the following guidelines:
RESET command is added as soon as the first waypoint is added.
RESET command executes, all the markers on the map and the list of waypoints are cleared.
standardMarkers are removed by making a call to removeAllMapObjects().
protected void commandRun(Command c) {
if (c == ADD) {
addCommand(RESET);
addWaypoint();
} else if (c == RESET) {
removeCommand(RESET);
addCommand(ADD);
map.removeAllMapObjects();
wpl = new WaypointParameterList();
}
}
}
4. As at least two waypoints have been added to the list, the calculation of a route may be requested which involves three steps
RouteFactoryWaypointParameterList along with the modes of transportation order to optimize the route selected
mapCanvas.
Note: The code in commandRun () in the example below adds a functioning CALCULATE Command. The doRouting() method prepares the screen whilst the calculateRoute() method sets up the necessary service object calls, Since the call to the routing service has been made asynchronously (i.e.
by passing in a RouteListener), a callback method onRequestComplete() clears the mapCanvas and adds the route received. clears the mapCanvas and adds the route received.
public class RoutingDemo extends Base implements RouteListener{
…
private WaypointParameterList wpl = new WaypointParameterList();
protected Route[] routes;
…
protected void commandRun(Command c) {
if (c == ADD) {
addCommand(RESET);
addWaypoint();
if (wpl.getWaypointCoordinates().length > 1) {
addCommand(CALCULATE);
}
} else if (c == CALCULATE) {
doRouting ( new RoutingMode[] { new RoutingMode() });
} else if (c == RESET) {
removeCommand(RESET);
addCommand(ADD);
map.removeAllMapObjects();
wpl = new WaypointParameterList();
routes = null;
}
}
private void doRouting(RoutingMode[] modes) {
addCommand(RESET);
removeCommand(ADD);
removeCommand(CALCULATE);
removeCommand(CALCULATE_WITH_MODE);
calculateRoute(modes);
}
private void calculateRoute(RoutingMode[] modes) {
RouteFactory rf = RouteFactory.getInstance();
rf.createRouteRequest().calculateRoute(wpl, modes, this);
}
public void onRequestComplete(RouteRequest request, Route[] routes) {
map.removeAllMapObjects();
this.routes = routes;
for (int i = 0; i < routes.length; i++) {
map.addMapObject(mapFactory.createOptMapPolyline(routes[i]
.getShape(), 3));
map.setCenter(routes[i].getStart().getMappedPosition());
}
}
public void onRequestError(RouteRequest request, Throwable error) {
map.removeAllMapObjects();
}
}
Result: You now have a working routing application
5. Add a command to make a cycle route request. Doing so involves passing the correct RoutingMode into the RouteManager.
Note: RouteManager supports a variety of forms of transport. The default route calculation is for a car but other modes such as walking or cycling
are also supported. This simply involves passing the correct RoutingMode into the RouteManager.
protected void commandRun(Command c) {
if (c == ADD) {
addCommand(RESET);
addWaypoint();
if (wpl.getWaypointCoordinates().length > 1) {
addCommand(CALCULATE);
}
} else if (c == CALCULATE) {
doRouting ( new RoutingMode[] { new RoutingMode() });
} else if (c == CALCULATE_WITH_MODE) {
RoutingMode mode = new RoutingMode();
mode.setTransportModes(new int[] { TransportMode.BICYCLE });
RoutingMode[] modes = { mode };
doRouting (modes);
} else if (c == RESET) {
removeCommand(RESET);
addCommand(ADD);
map.removeAllMapObjects();
wpl = new WaypointParameterList();
routes = null;
}
}
Result: You now have a routing application which offers both car and cycle routes.
Note: Routing requests can take some time and unless you provide the user with some visual feedback, the device appears to
hang. Thus consider displaying a text message over the map while the user waits. Remember that a MapCanvas is merely a subclass of Canvas and as such supports the paint() method. You can override MapCanvas.paint() ourselves in the Base class which itself is a subclass of MapCanvas.
In the following sample code, in order to facilitate user feedback, four helper methods have been added to the Base class: error(),note(),progressStart() and progressEnd().
private void doRouting(RoutingMode[] modes) {
progressStart("Calculating route", "Route not available");
addCommand(RESET);
removeCommand(ADD);
removeCommand(CALCULATE);
removeCommand(CALCULATE_WITH_MODE);
calculateRoute(modes);
}
public void onRequestComplete(RouteRequest request, Route[] routes) {
map.removeAllMapObjects();
this.routes = routes;
for (int i = 0; i < routes.length; i++) {
map.addMapObject(
mapFactory.createMapPolyline(routes[i].getShape(), 3));
map.setCenter(routes[i].getStart().getMappedPosition());
}
progressEnd();
}
public void onRequestError(RouteRequest request, Throwable error) {
map.removeAllMapObjects();
error(error.toString());
}
Result: Feedback appears on the screen whilst a route is calculated.
The following coded example displays the results of a location-based search. Search is a common use case for location base
services. The Maps API for Java ME is able to communicate with an associated searchManager service to return location-based search results based upon textual input.
1. Start with a minimal map example using the base class with a single associated SEARCH command button and a skeleton commandRun () method
public class SearchMIDlet extends MIDlet {
protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
}
protected void pauseApp() {
}
protected void startApp() throws MIDletStateChangeException {
ApplicationContext.getInstance().setAppID(...);
ApplicationContext.getInstance().setToken(...);
Display display = Display.getDisplay( this );
SearchDemo md = new SearchDemo(display, this );
display.setCurrent(md);
}
}
public class SearchDemo extends Base {
private final Command SEARCH = new Command("Search address", Command.OK, 3);
public SearchDemo(Display display, MIDlet midlet ) {
super(display,midlet);
addCommand(SEARCH);
}
protected void commandRun(Command c ) {
}
}
2. Add a TextBox for the search query which is closed by an OK command.
Note: Later, you will add code to the search method, so that a search will occur based upon the text entered before displaying
the result. Switching between the mapCanvas and the Textbox is a matter of setting the current Display
public class SearchDemo extends Base {
private final Command SEARCH = new Command("Search address", Command.OK, 3);
private final Command OK = new Command("Ok", Command.OK, 1);
private final TextBox searchBox = new TextBox("Enter address",
"London, Britain", 100, TextField.ANY);
…
protected void commandRun(Command c ) {
if( c == SEARCH ) {
searchBox.addCommand( OK );
searchBox.setCommandListener( this );
display.setCurrent(searchBox);
} else if( c == OK ) {
display.setCurrent(this);
removeCommand(SEARCH);
}
}
}
3. Make an asynchronous search request using a searchFactory where the application listens for the result to be returned, and centers the map on the first result found.
searchFactory is able to create an instance of GeoCodeRequest which handles all the underlying network calls involved in the search. In order to avoid the overhead of acquiring an instance
of searchFactory for each search, create the factory and hold a reference to it when the MIDlet starts up. When invoking the GeoCodeRequest itself, it is possible to make the search query either synchronous or asynchronous. This example makes an asynchronous search
so that the app is not frozen whilst it waits for the result. The app must implement a callback function in order to do this.
Thus when calling geoRequest.geocode(), an extra GeoCodeRequestListener parameter is added to the end of the list of method parameters.
GeoCodeRequest blocks the app until the results of the search have been completed. In order to do a synchronous search, no GeoCodeRequestListener is required.
geocode()method. This requires a text string to search. The area of search may be reduced by defining a spatial filter. In the example, this
is left as null, so the search will be worldwide.
onRequestComplete, further code will be added to process the results..
public class SearchDemo extends Base implements GeocodeRequestListener {
private final Command SEARCH = new Command("Search address", Command.OK, 3);
private final Command OK = new Command("Ok", Command.OK, 1);
private SearchFactory searchFactory =
SearchFactory.getInstance();
private Location[] locations; // search results
…
public void onRequestComplete(GeocodeRequest request, Location[] result) {
locations = result;
centerMapToFirstSearchResult();
addCommand(SEARCH);
}
public void onRequestError(GeocodeRequest request, Throwable error) {
locations = null;
error(error.toString());
addCommand(SEARCH);
}
private void centerMapToFirstSearchResult() {
if (null != locations && locations.length > 0) {
map.setCenter(locations[0].getDisplayPosition());
} else {
error("Location not found.");
}
}
}
Note: Calls to a search service can take some time, so it is best to display something on the screen telling the User to wait.
protected void commandRun(Command c ) {
if (c == SEARCH) {
map.removeAllMapObjects();
searchBox.addCommand(OK);
searchBox.setCommandListener(this);
display.setCurrent(searchBox);
} else if (c == OK) {
display.setCurrent(this);
removeCommand(SEARCH);
GeocodeRequest geocodeRequest = searchFactory.createGeocodeRequest();
geocodeRequest.geocode(searchBox.getString(), null, this);
progressStart("Searching address..", "Address was not found.");
}
}
}
4. Iterate through the results and add standardMarkers to all the locations returned by the search. Use the getDisplayPosition() method for each location to retrieve the appropriate GeoCoordinates.
A call is made to remove AllMapObjects() prior to displaying the search screen, which in turn clears the map prior to starting the search for aesthetic reasons in
order to avoid flickering.
public class SearchDemo extends Base {
…
private void markResults(){
for( int i = 0; i < locations.length; i++ ) {
MapStandardMarker marker = mapFactory.createStandardMarker(
locations[i].getDisplayPosition(), 10, null, null );
map.addMapObject( marker );
}
}
protected void commandRun(Command c ) {
if (c == SEARCH) {
map.removeAllMapObjects();
searchBox.addCommand(OK);
searchBox.setCommandListener(this);
…
}
public void onRequestComplete(GeocodeRequest request, Location[] result) {
locations = result;
centerMapToFirstSearchResult();
markResults();
addCommand(SEARCH);
}
The following coded example shares a chosen location with another device by sending an SMS.
1. Start with a minimal map that uses the Base class, since it displays a navigable, zoomable mapCanvas on the screen.
Note: You can also add the outline of the SEND_SMS Command in preparation of sending the location once selected.
public class SharingMIDlet extends MIDlet {
protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
}
protected void pauseApp() {
}
protected void startApp() throws MIDletStateChangeException {
ApplicationContext.getInstance().setAppID(...);
ApplicationContext.getInstance().setToken(...);
Display display = Display.getDisplay( this );
SharingDemo cd = new SharingDemo (display, this );
display.setCurrent(cd);
}
}
public class SharingDemo extends Base {
private final Command SEND_SMS = new Command("Share via SMS", Command.OK, 1);
public SharingDemo(Display display, MIDlet midlet, SharingConfig config) {
super(display, midlet);
addCommand(SEND_SMS);
}
protected void commandRun(Command c) {
}
}
2. Instantiate a SharingManager and call the getMapUrl() method to retrieve the encoded URL Of the map currently displayed on the mapCanvas.
Note: Sharing a location involves sending a static map to another device. To accomplish this, you can use Nokia's Map Image API for Http, which is designed to display a map of a location given an appropriately encoded URL. To create the URL, you can utilize an existing URL encoding mechanism within the Maps API for Java ME.
protected void commandRun(Command c) {
if (c == SEND_SMS ) {
SharingManager sm = SharingManager.getInstance();
String url = sm.getMapUrl(map);
}
}
3. To display the URL on the screen of the device, create a Form with a pair of TextFields and a couple of Commands.
public class SharingConfig extends Form {
final static Command SEND = new Command("Send", Command.OK, 10);
final static Command CANCEL = new Command("Cancel", Command.CANCEL, 20);
private TextField phoneNumber = new TextField("Number", "", 40, TextField.NUMERIC );
private TextField message = new TextField("Message", "", 160, TextField.ANY );
public SharingConfig() {
super("Settings");
append(phoneNumber);
append(message);
addCommand(CANCEL);
addCommand(SEND);
}
public String getPhoneNumber() {
return phoneNumber.getString();
}
public void setPhoneNumber(String number) {
phoneNumber.setString(number);
}
public void setMessage(String message) {
this.message.setString( message );
}
public String getMessage() {
return message.getString();
}
}
4. Alter the startApp() of the MIDlet in order to allow the SharingDemo class to display, interact with, and retrieve information from the Form
public class SharingMIDlet extends MIDlet {
protected void startApp() throws MIDletStateChangeException {
ApplicationContext.getInstance().setAppID(...);
ApplicationContext.getInstance().setToken(...);
SharingConfig messageConfig = new SharingConfig();
Display display = Display.getDisplay(this);
SharingDemo minimalMap = new SharingDemo(display, this, messageConfig);
display.setCurrent(minimalMap);
}
}
public class SharingDemo extends Base {
private final Command SEND_SMS = new Command("Share via SMS", Command.OK, 1);
private final SharingConfig config;
public SharingDemo(Display display, MIDlet midlet, SharingConfig config) {
super(display, midlet);
this.config = config;
addCommand(SEND_SMS);
config.setCommandListener(this);
}
…
}
5. Update the commandRun() method. This allows you to alternate between the MapCanvas and the Form by switching the display from one to the other using the setCurrent() method of the display.
protected void commandRun(Command c) {
if (c == SEND_SMS ) {
SharingManager sm = SharingManager.getInstance();
String url = sm.getMapUrl(map);
config.setPhoneNumber("");
config.setMessage(url);
display.setCurrent(config);
} else if ( c == SharingConfig.CANCEL ) {
display.setCurrent(this);
}
}
Note: The URL is displayed on screen when you select the SEND_SMS Command. If you enter the URL displayed into a web browser, a map focused on the location Selected will be displayed.
6. Send the information to another device by SMS. As this is a standard function of all devices, the code can be retrieved directly from a library of examples.
protected void commandRun(Command c) {
if (c == SEND_SMS ) {
SharingManager sm = SharingManager.getInstance();
String url = sm.getMapUrl(map);
config.setPhoneNumber("");
config.setMessage(url);
display.setCurrent(config);
} else if ( c == SharingConfig.CANCEL ) {
display.setCurrent(this);
} else if ( c == SharingConfig.SEND ) {
String message = config.getMessage();
String phoneNumber = config.getPhoneNumber();
sendSms( phoneNumber, message );
display.setCurrent(this);
}
}
public boolean sendSms(String number, String message){
boolean result = true;
try {
String addr = "sms://"+number;
MessageConnection conn = (MessageConnection) Connector.open(addr);
TextMessage msg = (TextMessage)conn.newMessage(MessageConnection.TEXT_MESSAGE);
msg.setPayloadText(message);
conn.send(msg);
conn.close();
} catch(SecurityException se) {
result = false;
} catch (Exception e) {
result = false;
}
return result;
}
Note: Since the MapCanvas is a subclass of Canvas,it supports the paint() method, which therefore can be overridden. A full discussion of paint() is beyond the scope of this example, but a note() helper method has been added to the Base class in order to display a message indicating success or failure.
protected void commandRun(Command c) {
if (c == SEND_SMS ) {
SharingManager sm = SharingManager.getInstance();
String url = sm.getMapUrl(map);
config.setPhoneNumber("");
config.setMessage(url);
display.setCurrent(config);
} else if ( c == SharingConfig.CANCEL ) {
display.setCurrent(this);
} else if ( c == SharingConfig.SEND ) {
String message = config.getMessage();
String phoneNumber = config.getPhoneNumber();
if ( sendSms( phoneNumber, message ) ) {
note("Sent to " + phoneNumber, 2500);
} else {
note("Could not send to " + phoneNumber, 2500);
}
display.setCurrent(this);
}
}
8. Make the application more usable by adding in code that responds to the keyReleased() event generated by the mapCanvas. Specifically, add code so that when the 5 key, which maps to Canvas.FIRE, is pressed, the SEND_SMS Command is executed.
Note: To respond to events, create a custom MapComponent implementing an EventListener and attach it to the mapCanvas. The overridden keyReleased() method only returns true, then the event has been successfully consumed. The example code uses a vanilla event framework defined in the Base class.
public class SharingDemo extends Base {
private final Command SEND_SMS = new Command("Share via SMS", Command.OK, 1);
private final SharingConfig config;
public SharingDemo(Display display, MIDlet midlet, SharingConfig config) {
super(display, midlet);
this.config = config;
addCommand(SEND_SMS);
config.setCommandListener(this);
// Mover component handles pointer events to move the marker
map.addMapComponent(new Mover());
}
class Mover extends MapComponentImpl {
public EventListener getEventListener() {
return new MoverEventListener();
}
}
class MoverEventListener extends EventListenerImpl {
public boolean keyReleased(int keyCode, int gameAction) {
if (gameAction == Canvas.FIRE) {
commandAction(SEND_SMS, null);
return true;
} else {
return false;
}
}
… etc
The use of the API is most powerful when combined with other web services in order to display locational information. Many
web services offer a data feed, typically in XML or JSON format, and these frequently contain some form of GeoCoordinates which serve to define the original location of the data.
An example of this use is the JSON feed from the Twitter service, which offers data in the format shown in the sample code below.
{"completed_in":0.082,
"max_id":1193495559555555555,
"max_id_str":"119349555555555555",
"next_page":"?page=2&max_id=119349555555555555&q=Hi",
"page":1,
"query":"Hi",
"refresh_url":"?since_id=119349555555555555&q=Hi",
"results":[
{"created_at":"Thu, 29 Sep 2011 09:55:27 +0000",
"from_user":"Example",
"from_user_id":555555555,
"from_user_id_str":"555555555",
"geo":{
"coordinates": [
40.0191219,
-105.2746654
],
"type": "Point"
},
"id":119349555555555555,
"id_str":"1193495559555555555",
"iso_language_code":"en",
"metadata":{"result_type":"recent"},
"profile_image_url":"http://a1.twimg.com/profile_images/15555555555/photo-example.JPG",
"source":"<a href="http://twitter.com/">web</a>",
"text":"hi there",
"to_user":"Test Data",
"to_user_id":127777755,
"to_user_id_str":"127777755"},
],
"results_per_page":15,
"since_id":0,
"since_id_str":"0"
}
Extracting Data
The example extracts data from the twitter JSON feed and adds StandardMarkers to display the GeoCoordinates of the locations of the tweets onto a MapCanvas. When a marker is clicked, more information is displayed about the tweet. Rather than building up this example from scratch,
important aspects of the complete coded example have been highlighted.
Central to the application is the getTweets() method which retrieves a JSONObject by requesting a URL, and breaks down the object into its constituent parts. The code retrieves information such as the user's
name, profile image URL and, most crucially, the user's location. The location is saved as a GeoCoordinate, whereas all the other fields can be saved as Strings. The extracted information from all of the tweets is returned as a Vector.
public Vector getTweets( GeoCoordinate coord, int km, String query, int count ) throws IOException {
Vector tweets = new Vector();
JSONObject obj = JSONParser.parse( createUrl(coord, km, query, count ) );
JSONArray results = (JSONArray) obj.get(KEY_RESULTS);
Enumeration resultsEnum = results.elements();
while( resultsEnum.hasMoreElements() ) {
JSONObject t = (JSONObject) resultsEnum.nextElement();
String user = (String) t.get(KEY_USER);
String profile_image_url = (String) t.get(KEY_IMAGE);
String location = (String) t.get(KEY_LOCATION);
String text = (String) t.get(KEY_TEXT);
GeoCoordinate gc = null;
Object geoTemp = t.get(KEY_GEO);
if (geoTemp != null && geoTemp instanceof JSONObject) {
JSONObject geo = (JSONObject) geoTemp;
Vector values = ((JSONArray)geo.get(KEY_COORDINATES));
gc = new GeoCoordinate(
Double.parseDouble((String) values.elementAt(0)),
Double.parseDouble((String) values.elementAt(1)), 0);
}
tweets.addElement(new Tweet( text, location, profile_image_url, user, gc ));
}
refresh = (String) obj.get( KEY_REFRESH );
return tweets;
}
Displaying Tweets
To display the tweets on the map, the example makes a call to the addTweet() method. This extracts the tweet data including the GeoCoordinate, and places a StandardMarker over the appropriate location.
Note: The line added_tweets.put(msm, t); is important. The variable added_tweets is a HashMap. This HashMap holds a copy of each tweet in memory using the StandardMarker created as its primary key. Thus an association between a marker and the tweet that created it is kept.
private void addTweet(Tweet t) {
if (t.getText() == null) {
// ignore tweets without text
return;
}
GeoCoordinate gc = t.getUserCoordinate();
boolean addTweet = true;
cleanOldest();
if (map.getZoomLevel() > 0) {
GeoBoundingBox gbb = new GeoBoundingBox(map.pixelToGeo(new Point(0,
0)), map.pixelToGeo(new Point(map.getWidth(), map
.getHeight())));
addTweet = gbb.contains(gc);
}
if (addTweet) {
MapStandardMarker msm = mapFactory.createStandardMarker(gc, 100,
"", null);
msm.setColor(MARKER_COLOR);
map.addMapObject(msm);
added_tweets.put(msm, t);
repaint();
}
}
Linking the Methods
The methods addTweet() and getTweet() are linked together by the method updateTweets(), which, as you would expect, is called periodically on its own Thread. This means that the Twitter feed will be updated at regular intervals.
private void updateTweets() throws Throwable {
Vector tweets = twitterRequest.getTweets(config.getPosition(),
config.getRadius(), config.getQuery(), config.getMaxCount());
for (Enumeration iterator = tweets.elements(); iterator
.hasMoreElements();) {
Tweet tweet = (Tweet) iterator.nextElement();
if (tweet.getUserCoordinate() == null) {
randomPlaceWithinRadius(tweet);
}
addTweet(tweet);
}
}
Defining the URL Request
The definition of the URL to request from the Twitter service is set up by the means of the createUrl() method. The createUrl() method accepts data from the TwitterConfig class. TwitterConfig is a Form where the user enters free text parameters such as location and the tweet text for which to look. The location free text
is changed to a GeoCoordinate in the moveToPlaceInSettings() method, which in turn uses SearchManager.geocode(). This also alters the default focus and zoom of the MapCanvas.
private String createUrl(GeoCoordinate coord, int km, String query,
int count) {
StringBuffer sb = new StringBuffer();
sb.append(SERVER);
if( refresh != null ) {
sb.append(refresh);
refresh = null;
} else {
sb.append( "q=" + urlencode( query ) );
}
sb.append( "&lang=en" );
sb.append( "&rpp=" + count );
sb.append( "&geocode=" + + coord.getLatitude() + "," + coord.getLongitude() + "," + km + "km" );
return sb.toString();
}
private void moveToPlaceInSettings() {
SearchManager sm = SearchManager.getInstance();
sm.geocode(config.getLocationString(), null);
Location[] locs = sm.getLocations();
if (locs.length > 0 && locs[0].getDisplayPosition() != null) {
config.setPosition(locs[0].getDisplayPosition());
}
sm.clear();
map.setCenter(config.getPosition());
// set correct zoom
double radius = config.getRadius();
double maxDegreeDiff = radius / SURFACE_DISTANCE;
GeoCoordinate cc = config.getPosition();
try {
GeoCoordinate topLeft = new GeoCoordinate(cc.getLatitude()
- maxDegreeDiff, cc.getLongitude() - maxDegreeDiff, 0);
GeoCoordinate bottomRight = new GeoCoordinate(cc.getLatitude()
+ maxDegreeDiff, cc.getLongitude() + maxDegreeDiff, 0);
GeoBoundingBox box = new GeoBoundingBox(topLeft, bottomRight);
map.zoomTo(box, true);
} catch (Throwable t) {
// errors ignored that don't need to check that center position plus
// maxDegreeDiff fits on map area
}
}
Interacting with Events
To Interact with MapCanvas events, the vanilla MapComponent from the Base class is overridden, and the vanilla EventListener implementation has been overridden with the Updater class. The Updater works if the cursor is currently centered over a StandardMarker, and if so, retrieves the data about the associated Tweet from the added_tweets HashMap.
class Updater extends MapComponentImpl {
Point center = new Point(map.getWidth() / 2, map.getHeight() / 2);
public void mapUpdated(boolean zoomChanged) {
MapObject mo = map.getObjectAt(center);
if (mo != null && mo instanceof MapStandardMarker) {
MapStandardMarker msm = (MapStandardMarker) mo;
current_tweet = (Tweet) added_tweets.get(msm);
addCommand(SHOW_TWEET);
removeCommand(UPDATE_TWEETS);
removeCommand(EDIT_CONFIG);
} else { // no tweet under cursor
current_tweet = null;
removeCommand(SHOW_TWEET);
addCommand(UPDATE_TWEETS);
addCommand(EDIT_CONFIG);
}
}
}
Displaying Details
The remaining showTweet() method is called whenever the SHOW_TWEET command is fired. It displays details about the currently selected tweet onto the screen. There is also code to retrieve
and an image to display from the Tweeter's profile by accessing their image URL.
private void showTweet() {
String text = current_tweet.getText();
if (text != null) {
String from = current_tweet.getFromUser();
Alert a = new Alert("" + from, text, null, AlertType.INFO);
a.setTimeout(Alert.FOREVER);
display.setCurrent(a, this);
String url = current_tweet.getProfileImageUrl();
if (url != null) {
// download and set user image to visible alert
log("Create image " + url);
Image img;
InputStream is = null;
try {
is = Connector.openInputStream(url);
img = Image.createImage(is);
a.setImage(img);
} catch (Throwable t) {
// do not show errors if image download fails
} finally {
try {
if (is != null) {
is.close();
}
}
catch (Throwable e) {
}
}
}
}
}
The following code example reads in a KML data file and displays the information on the map. KML is an XML notation for geographic applications. It is able to:
KML data support is reliant upon XML parsing, and is only available for phones that have JSR-172 web services support.
1. Start with a minimal map that uses the Base class, since it displays a navigable, zoomable mapCanvas on the screen.
Note: You can also add the outline of two Command buttons in preparation for interaction with the KML data once it has loaded.
public class KMLMIDlet extends MIDlet {
protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
}
protected void pauseApp() {
}
protected void startApp() throws MIDletStateChangeException {
ApplicationContext.getInstance().setAppID(...);
ApplicationContext.getInstance().setToken(...);
Display display = Display.getDisplay( this );
KMLDemo md = new KMLDemo (display, this );
display.setCurrent(md);
}
}
public class KMLDemo extends Base {
private final static Command SHOW_BALLOON_VIEW = new Command("Details", Command.OK, 1);
private final static Command SHOW_LIST_VIEW = new Command("List", Command.OK,
2);
public KMLDemo (Display display, MIDlet midlet) {
super(display, midlet);
}
protected void commandRun(Command c) {
if (c == SHOW_BALLOON_VIEW) {
} else if (c == SHOW_LIST_VIEW) {
}
}
}
2. Instantiate a KMLManager and call the parse() method on a known resource file to read in the KML data. Since KML parsing can take a long time, it makes sense to parse
the file asynchronously. This can be achieved by adding a KMLParserListener to the end of the method parameters. The KMLParserListener will provide the necessary callback functions.
Note: The coded example includes a KML file as a resource in the examples jar, but it would also be possible to load an arbitrary
KML file over the internet using the parseKML() method instead.
public class KMLDemo extends Base implements KMLParserListener{
…
public KMLDemo (Display display, MIDlet midlet) {
super(display, midlet);
progressStart("Parsing KML", "KML not parsed.");
parser.parse(getClass().getResourceAsStream("/earthquake.kml"), this);
}
public void onParseComplete(KMLManager source, Document document) {
progressEnd();
}
public void onParseError(KMLManager source, Throwable error) {
error("Error during KML parse: " + error.toString());
}
3. To display the KML data on a map, create a KMLFactory instance and call the createKMLResultSet() method. Once again this method may take time to complete, so it is better to create the KMLResultSet asynchronously. This is achieved by implementing the callback functions defined by a KMLFactoryListener. Once the KMLResultSet has been generated, it is simple to add the map objects to the mapCanvas, as they are already held in a mapContainer.
public class KMLDemo extends Base implements KMLParserListener, KMLFactoryListener {
…
public void onParseComplete(KMLManager source, Document document) {
progressEnd();
progressStart("Creating Map", "Map creation failed");
KMLFactory.getInstance(mapFactory).
createKMLResultSet(document, this);
}
public void onCreateKMLResultSetComplete(KMLFactory source, KMLResultSet resultSet) {
map.addMapObject(resultSet.getContainer());
map.zoomTo(resultSet.getContainer().getBoundingBox(), false);
progressEnd();
}
public void onParseError(KMLManager source, Throwable error) {
error("Error during KML parse: " + error.toString());
}
4. Add a KMLEventListener to indicate when a <Placemark> is selected and view the associated data. Viewing the details of a single <Placemark> is known as a balloon view in the KML specification. There is a KMLMapComponent associated with the generated KMLResultSet, which handles the highlighting of <Placemark> elements and the notification of which <Placemark> (if any) is currently in the center of the map. To wire this in you need to set the KMLEventListener on the KMLMapComponent and add the KMLMapComponent itself to the mapCanvas. The callback method onFocusChanged() will keep track of the current <Placemark> and hide or display the SHOW_BALLOON_VIEW Command button as necessary. A crude display of the <Placemark> data has been added as an Alert.
public class KMLDemo extends Base implements KMLEventListener, KMLParserListener, KMLFactoryListener {
private final static Command SHOW_BALLOON_VIEW = new Command("Details", Command.OK, 1);
private final static Command SHOW_LIST_VIEW = new Command("List", Command.OK,
2);
private Feature currentPlaceMark = null;
…
public void onCreateKMLResultSetComplete(KMLFactory source, KMLResultSet resultSet) {
map.addMapObject(resultSet.getContainer());
map.zoomTo(resultSet.getContainer().getBoundingBox(), false);
if (resultSet.getFeatures().length > 0) {
KMLMapComponent component = resultSet.getKMLMapComponent();
component.setEventListener(this);
map.addMapComponent(component);
}
progressEnd();
}
public void onFocusChanged(Feature placeMark) {
currentPlaceMark = placeMark;
if (currentPlaceMark != null) {
addCommand(SHOW_BALLOON_VIEW);
} else {
removeCommand(SHOW_BALLOON_VIEW);
}
}
protected void commandRun(Command c) {
if (c == SHOW_BALLOON_VIEW) {
showPlaceMarkDetails();
} else if (c == SHOW_LIST_VIEW) {
}
}
private void showPlaceMarkDetails() {
Alert a = new Alert(currentPlaceMark.getName(),currentPlaceMark.getDescription(), null, AlertType.INFO);
a.setTimeout(Alert.FOREVER);
display.setCurrent(a, this);
}
Note: A more elaborate balloon view display of the <Placemark> data has been added to the sample code in the examples jar. It assumes that the <description> element of the <Placemark> contains well formed XHTML, and extracts images and text directly from the <description> element. A typical <Placemark> could contain the following data:
<Placemark>
<name>Name of the placemark</name>
<description><![CDATA[<div>HTML Description</div>]]></description>
<address>address of the location</address>
<phone>012-345-6789</phone>
<Point>
<coordinates>-2.2211828827858,53.4393381986981,0</coordinates>
</Point>
</Placemark>
The coded balloon view displays the <name> element as the title of the Form, each line of text from the HTML description as an Item, and each image as a ImageItem
5. Add support for a list view display of the KML data. A list view is defined as a summary of all <Placemark> and <Folder> data in the KML specification. The KMLResultSet contains a getFeatures() method which holds the data to be displayed. The new Form merely needs to iterate through the <Folder>s and <Placemark>s and add Items to the screen.
public class KMLDemo extends Base implements KMLEventListener, KMLParserListener, KMLFactoryListener {
…
private KMLListView listView = null;
…
public void onCreateKMLResultSetComplete(KMLFactory source, KMLResultSet resultSet) {
map.addMapObject(resultSet.getContainer());
map.zoomTo(resultSet.getContainer().getBoundingBox(), false);
if (resultSet.getFeatures().length > 0) {
KMLMapComponent component = resultSet.getKMLMapComponent();
component.setEventListener(this);
map.addMapComponent(component);
listView = new KMLListView(resultSet);
listView.setCommandListener(this);
addCommand(SHOW_LIST_VIEW);
}
progressEnd();
}
protected void commandRun(Command c) {
if (c == SHOW_BALLOON_VIEW) {
showPlaceMarkDetails();
} else if (c == SHOW_LIST_VIEW) {
removeCommand(SHOW_LIST_VIEW);
display.setCurrent(listView);
} else if (c == KMLListView.OK) {
display.setCurrent(this);
}
public class KMLListView extends Form {
final static Command OK = new Command("Ok", Command.OK, 1);
private final Feature[] features;
private final ChoiceGroup choice;
public KMLListView(KMLResultSet resultSet) {
super("Settings");
features = resultSet.getFeatures();
String[] items = new String[features.length];
Image[] icons = new Image[features.length];
for (int i = 0; i < features.length; i++) {
String name = features[i].getName() != null
? features[i].getName()
: features[i].getType();
if (features[i].getType().equals(PlaceMark.ELEMENT)) {
name = " " + name;
}
items[i] = name;
}
choice = new ChoiceGroup("", Choice.MULTIPLE, items, icons);
setCheckboxes(true);
append(choice);
addCommand(OK);
}
private void setCheckboxes(boolean checked) {
for (int i = 0; i < choice.size(); i++) {
choice.setSelectedIndex(i, checked);
}
}
}
6. Extend the list view implementation to allow the user to alter the visibility of <Folder>s and <Placemark>s. The KMLResultSet holds all the map objects defined in the KML data file in a mapContainer. Add a recursive function to update the visibility of the map objects based on the selections made in the ChoiceGroup. The map can be refreshed by removing and re-adding the map objects to the mapCanvas.
Note: Since the current <Placemark> loses focus when the map is cleared, the mapFocusChanged()method will be fired again should the current <Placemark> remain visible.
protected void commandRun(Command c) {
if (c == SHOW_BALLOON_VIEW) {
showPlaceMarkDetails();
} else if (c == SHOW_LIST_VIEW) {
removeCommand(SHOW_LIST_VIEW);
display.setCurrent(listView);
} else if (c == KMLListView.OK) {
currentPlaceMark = null;
removeCommand(SHOW_BALLOON_VIEW);
display.setCurrent(this);
map.removeAllMapObjects();
map.addMapObject(listView.updateContainer());
addCommand(SHOW_LIST_VIEW);
repaint();
} else if (c == KMLBalloonView.OK) {
display.setCurrent(this);
}
}
public class KMLListView extends Form {
final static Command OK = new Command("Ok", Command.OK, 1);
private final Feature[] features;
private final ChoiceGroup choice;
private int choiceCount = 0;
private final MapContainer container;
…
public KMLListView(KMLResultSet resultSet) {
super("Settings");
container = resultSet.getContainer();
features = resultSet.getFeatures();
…
}
public MapContainer updateContainer() {
choiceCount = 0;
setVisibleGeometries(new MapObject[] { container}, true);
return container;
}
private void setVisibleGeometries(MapObject[] geometries, boolean parentVisible) {
for (int i = 0; i < geometries.length; i++) {
boolean visible = choice.isSelected(choiceCount) && parentVisible;
choice.setSelectedIndex(choiceCount, visible);
choiceCount++;
if (Folder.ELEMENT.equals(features[choiceCount - 1].getType())
|| Document.ELEMENT.equals(
features[choiceCount - 1].getType())) {
setVisibleGeometries(
((MapContainer) geometries[i]).getAllMapObjects(),
visible);
}
geometries[i].setVisible(visible);
}
}
}