Mapping points of interest using Java ME
This article explains how to use Nokia SDK 2.0 to create application for mapping points of interest for a Series 40 full touch device. The app also gets weather and news information from web services.
Article Metadata
Code Example
Article
Contents |
Prerequisites
- Download Nokia SDK 2.0 for Java
- To use Nokia Map API, you need to acquire a set of API Credentials from the Nokia Developer Registration page (registration is free)
- Acquire a key from worldweatheronline.com (a free weather information provider)
- Keep an eye on the notes and tips below :-)
Quick Walk through
The code example uses the content of a Refreshment app. The app provides information about current weather, contains news, and includes mapping features to help you locate refreshment.
In future the app may be extended to include navigation information with transits, walk and drive, or to additionally help users locate ATM's, bus stops, metro stations, nearby taxi stand and many more.
Quick intro to Location API
We need the current location to request Weather data to the server. I will explain how to request to the server to obtain weather data later in the coming topics. Current location is also used with maps to find out the nearest chat center and hangouts.
Creating object of location
LocationProvider myloc;Current location through Cellular ID
Series 40 supports cellular ID and WLAN positioning. Series 40 extends the Location API with the com.nokia.mid.location.LocationUtil class. Therefore, I have used LocationUtil.getLocationProvider(type, null). However, support for this in specific devices vary. So, I will be using MTE_CELLID, MTY_NETWORKBASED and MTA_ASSISTED.
Use the following statements in startApp() method.
int[] type = {Location.MTE_CELLID|Location.MTY_NETWORKBASED|Location.MTA_ASSISTED};
try {
myloc = LocationUtil.getLocationProvider(type, null);
javax.microedition.location.Location Loc = myloc.getLocation(300);
if(Loc != null){
QualifiedCoordinates cor = Loc.getQualifiedCoordinates();
lat = cor.getLatitude();
lon = cor.getLongitude();
}
} catch (Exception ex) {}
Quick intro to Nokia Map API
Creating object of MapCanvas and setting up the things
In startApp() method add the following lines to setup initial things.
ApplicationContext.getInstance().setAppID("Your_App_ID");
ApplicationContext.getInstance().setToken("Your_App_Token");
Positioning the map to current location and zooming
We will start with positioning to current location on map and extend this location to find out refreshment places near.
mapCanvas.getMapDisplay().zoomTo(new GeoBoundingBox(new GeoCoordinate(lat, lon, 0)), false);
mapCanvas.getMapDisplay().addMapObject(mapCanvas.getMapFactory().createStandardMarker(new GeoCoordinate(lat, lon, 0), 1, "It's You", 2));
Nokia UI Components used in here
Apart from using pure LCDUI components, I will make use of two new components provided and supported on Series 40 full touch devices. These are Category Bar and Icon Command.
CategoryBar
Creating Category Bar and making it visible.
CategoryBar cbar;
try {
Image[] unsel = { Image.createImage("/home.png"),
Image.createImage("/news.png"),
Image.createImage("/refreshment.png") };
Image[] sel = { Image.createImage("/home.png"),
Image.createImage("/news.png"),
Image.createImage("/refreshment.png") };
String[] lab = { "Home", "News", "Refreshments" };
cbar = new CategoryBar(unsel, sel, lab);
cbar.setElementListener(this);
cbar.setTransitionSupport(true);
cbar.setVisibility(true);
} catch (Exception ex) {}
IconCommand
Creating and using Icon Command is like normal command. More on Icon Command
Weather feed
Weather feed and rss feed data will be in XML format and we need to parse these xml to extract required information. There are number of XML parsers available: About XML Parser. kXML is one of such parser I would like to use. kXML 1.2 have been included in source program.
public void run() {
try {
//Url format for http request is: http://free.worldweatheronline.com/feed/weather.ashx?q=<latitude>,<longitude>&format=xml&num_of_days=1&key=XXXXXXXXXXXXXXXXXXXXXX
// or http://free.worldweatheronline.com/feed/weather.ashx?q=<City>,<Country>&format=xml&num_of_days=1&key=XXXXXXXXXXXXXXXXXXXXXX
String reqUrl = url + lat + "," + lon + keys;
con = (HttpConnection) Connector.open(reqUrl, Connector.READ_WRITE,true);
con.setRequestMethod(HttpConnection.GET);
weatherxmlparser(con.openDataInputStream());
} catch (Exception e) {
}
}
<data>
<request>
<type>LatLon</type>
<query>Lat 28.64 and Lon 77.24</query>
</request>
<current_condition>
<observation_time>11:10 AM</observation_time>
<temp_C>33</temp_C>
<temp_F>91</temp_F>
<weatherCode>353</weatherCode>
<weatherIconUrl>
<![CDATA[
http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0009_light_rain_showers.png
]]>
</weatherIconUrl>
<weatherDesc>
<![CDATA[ Light rain shower ]]>
</weatherDesc>
<windspeedMiles>11</windspeedMiles>
<windspeedKmph>17</windspeedKmph>
<winddirDegree>60</winddirDegree>
<winddir16Point>ENE</winddir16Point>
<precipMM>1.4</precipMM>
<humidity>59</humidity>
<visibility>3</visibility>
<pressure>998</pressure>
<cloudcover>75</cloudcover>
</current_condition>
<weather>
<date>2012-07-25</date>
<tempMaxC>37</tempMaxC>
<tempMaxF>99</tempMaxF>
<tempMinC>28</tempMinC>
<tempMinF>82</tempMinF>
<windspeedMiles>10</windspeedMiles>
<windspeedKmph>16</windspeedKmph>
<winddirection>E</winddirection>
<winddir16Point>E</winddir16Point>
<winddirDegree>87</winddirDegree>
<weatherCode>116</weatherCode>
<weatherIconUrl>
<![CDATA[
http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0002_sunny_intervals.png
]]>
</weatherIconUrl>
<weatherDesc>
<![CDATA[ Partly Cloudy ]]>
</weatherDesc>
<precipMM>8.7</precipMM>
</weather>
</data>
Above is sample XML data response. We need to parse the xml data to obtain required information. Important information required observation_time, temp_C, weatherIconUrl and weatherDesc. We need to design the XML parser to parse the weather feed. XML parser for this will be one below:
public void weatherxmlparser(InputStream xmldata) {
Reader r = new InputStreamReader(xmldata);
try {
XmlParser parser = new XmlParser(r);
traverse( parser, "" );
} catch (Exception e) {
}
}
public void traverse(XmlParser parser, String indent) {
try {
boolean leave = false;
do {
ParseEvent event = parser.read();
ParseEvent pe;
switch (event.getType()) {
// For example, <title>
case Xml.START_TAG:
// see API doc of StartTag for more access methods
// Pick up Time for display
if ("observation_time".equals(event.getName())) {
pe = parser.read();
this.r.wtr.time = pe.getText();
}
// Pick up temperature for display and so on
if ("temp_C".equals(event.getName())) {
pe = parser.read();
this.r.wtr.tempINDegC = pe.getText();
}
if ("weatherIconUrl".equals(event.getName())) {
pe = parser.read();
//download image for the corresponding URL
this.r.wtr.wicon = loadImage(pe.getText());
}
if ("weatherDesc".equals(event.getName())) {
pe = parser.read();
this.r.wtr.climate = pe.getText();
}
traverse( parser, "" ) ; // recursion call for each <tag></tag>
break;
// For example </title>
case Xml.END_TAG:
leave = true;
break;
// For example </data>
case Xml.END_DOCUMENT:
leave = true;
break;
// For example, the text between tags
case Xml.TEXT:
break;
case Xml.WHITESPACE:
break;
default:
}
} while (!leave);
} catch (Exception e) {
}
}

Weather data obtained from server is parsed and painted on Canvas of a CustomItem.
News Feed
To get latest new, we will be using Yahoo News RSS feed.
| Category | Feed URL |
|---|---|
| Top Stories | http://news.yahoo.com/rss/topstories |
| World News | http://news.yahoo.com/rss/world |
| Entertainment | http://news.yahoo.com/rss/entertainment |
| Movies | http://news.yahoo.com/rss/movies |
In news form, user selects an choice from choice group. Through itemState listener we are able to identify the change in the selection. Depending upon the selection, request to server is sent through HTTP request. Which is done using Thread. Using Thread because server may busy at the time of request and may not respond to our request. Therefore we make repeated request until server responds to our request. Then by opening I/O streams we can read the data. The thread t handles the task of requesting and opening I/O connection.
public void run() {
try {
con = (HttpConnection) Connector.open(yahooNewURL,Connector.READ_WRITE, true);
con.setRequestMethod(HttpConnection.GET);
getData(con.openInputStream());
} catch (Exception e) {
// TODO: handle exception
}
}
<rss xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
<channel>
<title>Movies News Headlines - Yahoo! News</title>
<link>http://news.yahoo.com/movies/</link>
<description>
Get the latest Movies news headlines from Yahoo! News. Find breaking Movies news, including analysis and opinion on top Movies stories, photos and more.
</description>
<language>en-US</language>
<copyright>Copyright (c) 2012 Yahoo! Inc. All rights reserved</copyright>
<pubDate>Mon, 23 Jul 2012 20:19:11 -0400</pubDate>
<ttl>5</ttl>
<image>
<title>Movies News Headlines - Yahoo! News</title>
<link>http://news.yahoo.com/movies/</link>
<url>http://l.yimg.com/a/i/us/nws/th/main_142c.gif</url>
</image>
<item>
<title>"Dark Knight Rises" earns $160.8 million in debut</title>
<description>
<p><a href="http://news.yahoo.com/dark-knight-rises-earns-160-8-million-debut-001911324--finance.html"><img src="http://l3.yimg.com/bt/api/res/1.2/34Jg14ltXARef75.MHa9rw--/YXBwaWQ9eW5ld3M7Zmk9ZmlsbDtoPTg2O3E9ODU7dz0xMzA-/http://media.zenfs.com/en_us/News/Reuters/2012-07-24T002136Z_1_CBRE86N010600_RTROPTP_2_FILM-US-USA-SHOOTING-BOXOFFICE-OFFICIAL.JPG" width="130" height="86" alt="A poster for "The Dark Knight Rises" is displayed in Burbank" align="left" title="A poster for "The Dark Knight Rises" is displayed in Burbank" border="0" /></a>LOS ANGELES (Reuters) - Warner Bros said on Monday "The Dark Knight Rises" took in $160.8 million at U.S. and Canadian box offices over the weekend, which is lower than industry estimates for the debut that felt the impact of last week's movie theater massacre in Colorado. "Dark Knight Rises" also opened in 17 international markets with $88 million in ticket sales to put its global total at just under $250 million for its debut. Warner Bros, a unit of Time Warner Inc, made no initial comment beyond simply releasing the figures. ...</p><br clear="all"/>
</description>
<link>
http://news.yahoo.com/dark-knight-rises-earns-160-8-million-debut-001911324--finance.html
</link>
<pubDate>Mon, 23 Jul 2012 20:19:11 -0400</pubDate>
<source url="http://www.reuters.com/">Reuters</source>
<guid isPermaLink="false">
dark-knight-rises-earns-160-8-million-debut-001911324--finance
</guid>
<media:content url="http://l3.yimg.com/bt/api/res/1.2/34Jg14ltXARef75.MHa9rw--/YXBwaWQ9eW5ld3M7Zmk9ZmlsbDtoPTg2O3E9ODU7dz0xMzA-/http://media.zenfs.com/en_us/News/Reuters/2012-07-24T002136Z_1_CBRE86N010600_RTROPTP_2_FILM-US-USA-SHOOTING-BOXOFFICE-OFFICIAL.JPG" type="image/jpeg" width="130" height="86"/>
<media:text type="html">
<p><a href="http://news.yahoo.com/dark-knight-rises-earns-160-8-million-debut-001911324--finance.html"><img src="http://l3.yimg.com/bt/api/res/1.2/34Jg14ltXARef75.MHa9rw--/YXBwaWQ9eW5ld3M7Zmk9ZmlsbDtoPTg2O3E9ODU7dz0xMzA-/http://media.zenfs.com/en_us/News/Reuters/2012-07-24T002136Z_1_CBRE86N010600_RTROPTP_2_FILM-US-USA-SHOOTING-BOXOFFICE-OFFICIAL.JPG" width="130" height="86" alt="A poster for "The Dark Knight Rises" is displayed in Burbank" align="left" title="A poster for "The Dark Knight Rises" is displayed in Burbank" border="0" /></a>LOS ANGELES (Reuters) - Warner Bros said on Monday "The Dark Knight Rises" took in $160.8 million at U.S. and Canadian box offices over the weekend, which is lower than industry estimates for the debut that felt the impact of last week's movie theater massacre in Colorado. "Dark Knight Rises" also opened in 17 international markets with $88 million in ticket sales to put its global total at just under $250 million for its debut. Warner Bros, a unit of Time Warner Inc, made no initial comment beyond simply releasing the figures. ...</p><br clear="all"/>
</media:text>
<media:credit role="publishing company"/>
</item>
</channel>
</rss>
Above shows the sample RSS feed in XML format received from server. We need to parse this XML to retrieve the required information. We are interested in reading title, link and published date. Therefore our parser should be designed to read only these fields located between <item> and </item> tag. So we do recursive parsing between each <item>and </item> tags until the end of document is reached.
Code for the same will be:
public void getData(InputStream xmldata) {
Reader r = new InputStreamReader(xmldata);
try {
XmlParser parser = new XmlParser(r);
ParseEvent pe = null;
parser.skip();
parser.read(Xml.START_TAG, null, "rss");
parser.skip();
parser.read(Xml.START_TAG, null, "channel");
boolean trucking = true;
while (trucking) {
pe = parser.read();
if (pe.getType() == Xml.START_TAG) {
String name = pe.getName();
if (name.equals("item")) {
String title, link, description;
title = link = description = null;
while ((pe.getType() != Xml.END_TAG)
|| (pe.getName().equals(name) == false)) {
pe = parser.read();
if (pe.getType() == Xml.START_TAG
&& pe.getName().equals("title")) {
pe = parser.read();
title = pe.getText();
} else if (pe.getType() == Xml.START_TAG
&& pe.getName().equals("link")) {
pe = parser.read();
link = pe.getText();
} else if (pe.getType() == Xml.START_TAG
&& pe.getName().equals("pubDate")) {
pe = parser.read();
description = pe.getText();
}
}
this.r.newsf.append(new customlist(title, link,
description, this.r));
} else {
while ((pe.getType() != Xml.END_TAG)
|| (pe.getName().equals(name) == false))
pe = parser.read();
}
}
if (pe.getType() == Xml.END_TAG && pe.getName().equals("rss")){
trucking = false;
t = null;
}
}
} catch (Exception e) {
}
}
Once we get these required information, we need to append this to our form. Which is done using this.r.newsf.append(new customlist(title,link,description, this.r)); . Where r - instance of Refreshment MIDlet, newsf is the form where we need to append.
Finally after parsing and appending the whole XML document. News Form looks like as above.
Working with MapCanvas
Bookmarking the location
Bookmarking feature enables to save the favorite locations on mobile. You visit some place or hotels, you may forget the address or name of the hotel. So using bookmark feature you can tag and save the current location in mobile phone.
if(c == bookmarkCommand){
Display.getDisplay(this).setCurrent(bookmark);
}
else if(c == okCommand){
writeSavedData(bookmark.getString(), lat, lon);
Display.getDisplay(this).setCurrent(mapCanvas);
}
else if(c == deleteCommand){
clearbookmark();
homef.deleteAll();
homef.append(wtr);
readSavedData();
}
Locating bookmarked location on map
public void updateMap(double lat, double lon, String name){
mapCanvas.getMapDisplay().removeAllMapObjects();
mapCanvas.getMapDisplay().reconnect();
mapCanvas.getMapDisplay().zoomTo(
new GeoBoundingBox(new GeoCoordinate(lat, lon, 0)), false);
mapCanvas.getMapDisplay().addMapObject(
mapCanvas.getMapFactory().createStandardMarker(
new GeoCoordinate(lat, lon, 0), 1, name, 2));
Display.getDisplay(this).setCurrent(mapCanvas);
}
Saving, Loading and Clearing bookmarks
We will be using record store to save the bookmark location, including name and date of bookmark using RMS.
We need a some structured way to store - latitude, longitude, date of bookmark and bookmark name. So that we can retrieve and use the same point to locate on map. Fist let us construct a schema to store in our database (record store).
| Type | Column 1 | Column 2 | Column 3 | Column 4 |
|---|---|---|---|---|
| Field name | Latitude | Bookmark name | Longitude | Bookmark date |
| Datatype | double | String | double | String |
Saving Bookmark name in between Latitude and Longitude is that we can differentiate two double value, i.e String between them acts as a delimiter.
public void readSavedData() {
homef.append("Recient Visit");
openRefreshmentRecordStore();
try {
if(refreshmentRecordStore.getNumRecords() == 0){ /*Checking the record store for records. if total no. of record is zero*/
homef.append("\nYou have not visited any place."); /* append some string like, you have not bookmarked any places */
}
byte[] byt = new byte[1000]; /* read the records from record store and add them to home screen */
ByteArrayInputStream Istream = new ByteArrayInputStream(byt);
DataInputStream dinp = new DataInputStream(Istream);
for (int i = 1; i <= refreshmentRecordStore.getNumRecords(); i++) {
refreshmentRecordStore.getRecord(i,byt,0);
double lat = dinp.readDouble(); /* latitude is a double value, that is stored at first column */
String place = dinp.readUTF(); /* Favorite name a string value, that is stored at second column */
double lon = dinp.readDouble(); /* longitude is double value, that is stored at third column */
String date = dinp.readUTF(); /* date is string value, that is stored at fourth column */
homef.append(new recentvisitlist(place, lat, lon,date,this)); /*create a list item with these value and append to home form*/
dinp.reset();
}
dinp.close();
Istream.close();
} catch (Exception e) {
}closeRefreshmentRecordStore();
}
public void writeSavedData(String title, double lat, double lon) {
DateField now = new DateField("",DateField.DATE_TIME); /* get current system date and time */
now.setDate(new Date());
String date = now.getDate().toString(); /* convert the date to string, so that we can store it in record store */
openRefreshmentRecordStore(); /* open record store and save */
try{
ByteArrayOutputStream Ostream = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(Ostream);
dout.writeDouble(lat);
dout.writeUTF(title);
dout.writeDouble(lon);
dout.writeUTF(date);
dout.flush();
byte[] byt = Ostream.toByteArray();
refreshmentRecordStore.addRecord(byt, 0, byt.length);
Ostream.close();
dout.close();
}catch (Exception e) {
// TODO: handle exception
}
closeRefreshmentRecordStore();
}
public void clearbookmark(){
try{
RecordStore.deleteRecordStore("refreshmentDataBase"); /* to clear all bookmark, delete the record store */
}catch (Exception e) {
// TODO: handle exception
}
}
More with maps
You can add functions like finding route between two locations, searching an address and locating them on map. Since there are wiki articles on working on map, I am not covering them here in this article or example.





Contents
Hamishwillee - Code looks nice, article a bit rough around the edges
Hi Adarsha
The best improvement you can make to this one is in the introduction/quick walkthrough - this basically says that the app is about mapping Points of interest, but the app does a whole lot more - displaying news and weather. I would mention these and how it does it (ie using RSS feeds or whatever) and also provide the screenshots up the top as part of the introduction - "a picture tells a thousand words". Upshot is that the introduction sets the scene for the rest of the document, so its worth saying what you're going to cover, and what developers can learn.
In terms of "wiki tidy up" I would use the Icode template for marking up code fragments inline rather than italic, and I'd go through all the code fragments and improve the indentation. Usually I use two or four spaces as a level of indentation.
In terms of the individual sections, I think you could say a lot more than just code dumping. I would use less headings, and certainly more meaningful headings. I would provide brief overviews of what APIs you've used, with links.
So for example, "Quick intro to Location API" has headings "Creating object of location" and "Current location through Cellular ID", neither of which have much content. YOu could hear provide an overview of the location API and links to references in the JDL or the wiki category page. You could explain that the code provides hints to the provider on the type of location information you want - ie cellular, gps etc and why you've specified all three. You could explain that the system will use the best options possible. You also imply that this section gets points of interest, but actually it only gets your current postion - it doesn't matter if you only plot your own position, but you should make it clear what you provide and what you don't.
Does that make sense?
I could say similar things about all the sections - for parsing the feed what XML do you use, what JSR is it in. Is there any reference material for XML parser people can use. normally I'd just have a brief overview explaining what sort of parser it is and link to the information for more. A REALLY good article would then go on to explain any particular tips and tricks - why you did it a certain way, not just what you did.
I think the code looks fine (although not a java expert).
Regards
Hamishhamishwillee 09:31, 25 July 2012 (EEST)
Jasfox - Why does the code example include its own XML parser?
Is there a good reason for including your own XML parser in the example? The complexity of the code could be reduced by importing the standard JSR 172 SAX parser library directly. This would make the code easier to read.jasfox 13:50, 27 July 2012 (EEST)
Hamishwillee - Agree with Jasfox here
Where possible, best to use the native libraries.hamishwillee 07:26, 30 July 2012 (EEST)
Adarsha saraff - @Jasfox and Hamish
I thought most of the developers are familiar with kXML parser. I didn't got this in my mind.
I could have done, but time was short and I am more familiar in using kXML parser.adarsha_saraff 08:32, 30 July 2012 (EEST)
Hamishwillee - Fair enough
Something to think about if you revise this though - on a resource constrained device it makes sense to use the inbuilt parser - for smaller and faster code.hamishwillee 04:48, 31 July 2012 (EEST)