Learn how to make your own customizable mapping app using Mobile Processing and the GPS on your N95.
In my last article about Mobile Processing and the N95, I showed you how to use the mLocation library to easily get the geoposition of your phone. It only took a few lines of code and was remarkably simple.
Now I’m going to show you how to use this feature along with the Yahoo! Maps API to make a simple mapping application similar to the Maps application that comes pre-installed on the N95. It won’t be quite as fancy, but since you will have the actual code, you’ll be able to customize it however you would like.
If you need help installing Mobile Processing, please refer back to my previous article on the topic. I’m going to assume here that you know how to export MIDlets to the phone, install, and run them – so I’ll concentrate on the code itself.
Our GPS Grabber
Here’s the code we used to grab the GPS data from the phone:
import mjs.processing.mobile.mlocation.*;
PFont font;
double[] coords = new double[2];
void setup() {
font = loadFont();
textFont(font);
textAlign(CENTER);
MLocation.location(coords);
}
void draw() {
background(0);
text(”Latitude : ” + coords[0], width/2, height/2-10);
text(”Longitude : ” + coords[1], width/2, height/2+10); }
Remember that doing so requires the mLocation library. If you haven’t installed that, please refer to my previous article on the subject.
Remember, also, that if you run this on the emulator on your computer you’ll probably get an error since there is no GPS unit to access.
The key parts for grabbing GPS data are:
import mjs.processing.mobile.mlocation.*;
This imports the library so Mobile Processing knows what the new functions are.
double[] coords = new double[2];
This sets up our array which will be populated with GPS data.
MLocation.location(coords);
This grabs the latitude and longitude of the GPS device itself, in this case your N95.
And then “coords[0]“ holds the latitude and “coords[1]“ the longitude. You can use those anywhere as normal variables.
Thats the extent of it.
Yahoo! Maps API
Let’s set that aside for a moment and write another program which grabs a map from Yahoo! and displays it. To begin, we’ll just hard-code in a geolocation. After we get this running properly we’ll add the GPS-grabbing code. Why? For one thing, we will save time if we don’t have to deploy to the phone every time we want to test the Yahoo! Maps API piece.
(An “API,” by the way, is an “application programming interface.” If you’re not familiar with this concept, don’t worry. I’ll guide you through it and you’ll see that APIs aren’t really that scary. In fact, they’re the backbone of the new open web. Most major sites have APIs of some flavor.)
The specific Yahoo! Maps service we’ll be using is the Yahoo! Map Image API. This service lets us grab an image of a map of whatever size we’d like, centered on whatever location we’d like. You might want to take a moment to poke around the Yahoo! page about this service, just to get an idea of what it’s all about.
The Code
To start, I’m going to dump a huge pile of code on you. Then we’re going to go through it and look at exactly what’s going on. Have no fear!
PClient clientXML;
PClient clientMap;
PRequest requestXML;
PRequest requestMap;
PFont font;
PImage mapImage;
String xml;
String mapURL;
// Battery Park, NY
double latitude = 40.703717;
double longitude = -74.016094;
void setup() {
clientXML = new PClient(this, “local.yahooapis.com”);
clientMap = new PClient(this, “gws.maps.yahoo.com”);
font = loadFont();
textFont(font);
noLoop();
}
void draw() {
background(0);
fill(255);
if( mapImage != null ) {
image(mapImage, width/2-mapImage.width/2, height/2- mapImage.height/2);
} else {
text(”Getting map…”, 4, 4, width - 8, height - 8);
requestXML = clientXML.GET(”/MapsService/V1/mapImage?
appid=nseriesdemo&latitude=” + latitude + “&longitude=” + longitude + “&zoom=2&image_height=” + height + “&image_width=” + width);
}
}
void libraryEvent(Object library, int event, Object data) {
if (library == requestXML) {
if (event == PRequest.EVENT_CONNECTED) {
requestXML.readBytes();
} else if (event == PRequest.EVENT_DONE) {
xml = new String((byte[]) data);
int start = xml.indexOf(”instance\”>”) + 10;
int end = xml.indexOf(”");
mapURL = xml.substring(start, end);
requestXML.close();
requestMap = clientMap.GET(mapURL.substring(25));
}
}
if (library == requestMap) {
if (event == PRequest.EVENT_CONNECTED) {
requestMap.readBytes();
} else if (event == PRequest.EVENT_DONE) {
mapImage = loadImage((byte[]) data);
requestMap.close();
redraw();
}
}
}
Yeah, that’s a lot of code. Let’s break it into four parts to see how it works.
Here’s the first part:
PClient clientXML;
PClient clientMap;
PRequest requestXML;
PRequest requestMap;
PFont font;
PImage mapImage;
String xml;
String mapURL;
// Battery Park, NY
double latitude = 40.703717;
double longitude = -74.016094;
These are our global variables. We’re going to grab the Yahoo! map image over the network, so we need to sent up a Processing Client and Request (”PClient” and “PRequest”) object for each network call.
Grabbing a map image from Yahoo! requires two network requests, one for an XML file which contains the image URL and then one for the image itself. It’s a bit weird, this two-stage process. But that’s how Yahoo! does it. So we make a PClient and PRequest for each, the XML and the map image.
After this we set up our other variables - the font, the image itself and some strings. Note that these objects that start with a “P” - “PFont”, “PImage” - are Mobile Processing-specific objects.
And finally we hard-wire some coordinates. We’ll hook them up to GPS later. For now, I’m just using the location of Battery Park in Manhattan. These variables are of the “double” type, as well. We must use this type in J2ME when we want to have decimal places. The “float” data type won’t work.
The second part, now:
void setup() {
clientXML = new PClient(this, “local.yahooapis.com”);
clientMap = new PClient(this, “gws.maps.yahoo.com”);
font = loadFont();
textFont(font);
noLoop();
}
If you’ve been following along with my Mobile Processing articles, this should be very familiar. It’s the standard Mobile Processing setup function. It runs once, right when you launch the app.
You’ll see we start by setting up our requests. You just have to set the domains you’re going to pull data from, in this case the two Yahoo! Maps API domains. Then we set up the font stuff - nothing terribly complex there. Then we put the application in “noLoop()” mode. This means that the “draw” function will run just once and then stop. If we want to run “draw” again, we have to specifically ask for it.
And our third part:
void draw() {
background(0);
fill(255);
if( mapImage != null ) {
image(mapImage, width/2-mapImage.width/2, height/2- mapImage.height/2);
} else {
text(”Getting map…”, 4, 4, width - 8, height - 8);
requestXML = clientXML.GET(”/MapsService/V1/mapImage?
appid=CHANGETHIS&latitude=” + latitude + “&longitude=” + longitude + “&zoom=2&image_height=” + height + “&image_width=” + width);
}
}
This, also, should look familiar to you. It’s our “draw” function - which because we called “noLoop()” will just run once unless we specifically ask for it again.
“background(0)” sets our background to black. “fill(255)” sets the text color to white.
Then we do something tricky. We say “if the map image has some data (aka, if it’s not null), then draw the image on the screen. Otherwise, write text saying that we’re getting the map.” This will make a bit more sense after we look at the fourth part of our code. Then - if the map image is not loaded - we do our network request to get that image. That’s the “requestXML” line.
Note! See in that last line where it says “appid=CHANGETHIS”? Change “CHANGETHIS” to be something else. Anything made out of letters and numbers. Yahoo! uses this to keep track of you and each appid can only grab 50,000 map images per day. Make it something like “myname” or something like that. If you read that line you should see how we’re requesting our image. You’ll see a “latitude” variable in there. And “longitude”. And “zoom”. The lower this number, the closer the zoom.
Play with it, if you’d like. And the “image_height” and “image_width”.
For these, we’re going to use two variables hard-wired in to Mobile
Processing: “height” and “width”. These are automatically set to the screen size of the device you’re using, so we don’t have to worry about figuring out the exact resolution of the N95 screen. This gives it to us for free (and makes it somewhat easier to write cross-platform apps).
So here’s that fourth part:
void libraryEvent(Object library, int event, Object data) {
if (library == requestXML) {
if (event == PRequest.EVENT_CONNECTED) {
requestXML.readBytes();
} else if (event == PRequest.EVENT_DONE) {
xml = new String((byte[]) data);
int start = xml.indexOf(”instance\”>”) + 10;
int end = xml.indexOf(”");
mapURL = xml.substring(start, end);
requestXML.close();
requestMap = clientMap.GET(mapURL.substring(25));
}
}
if (library == requestMap) {
if (event == PRequest.EVENT_CONNECTED) {
requestMap.readBytes();
} else if (event == PRequest.EVENT_DONE) {
mapImage = loadImage((byte[]) data);
requestMap.close();
redraw();
}
}
}
Okay. So this is some weird stuff. If you’re new to Java or Mobile Processing, you might have to accept for now that it does, in fact, work. You can take your time learning exactly how it works. I’m going to give a quick run-through, but it’s a bit weird.
This “libraryEvent” function basically listens to the client requests and does things based on what’s going on with those. So, when we ran this line in the “setup” function:
requestXML = clientXML.GET(”/MapsService/V1/mapImage?
appid=CHANGETHIS&latitude=” + latitude + “&longitude=” + longitude + “&zoom=2&image_height=” + height + “&image_width=” + width);
When we run this it fires off the “PRequest.EVENT_CONNECTED” library event when successfully connected, at which point it executes the line “requestXML.readBytes();” which reads the data from that URL.
Make sense, sort of? Okay. Then when it’s done reading that data, it fires off the “PRequest.EVENT_DONE” code, which here takes that data, puts it into a string called “xml” and then pulls out the URL of the map image that we want. Then it takes that and does another request for the actual map image (in PNG format). The block of code starting “if (library == requestMap) {” then takes care of that.
This is complicated, but it’s necessary to wrap your head around if you want to really understand how to make requests for data over the network using Mobile Processing.
Francis Li has another good example called Fetch on the Mobile Processing site, and of course take a look at the reference pages for PClient and PRequest.
Running the App
Okay. So save that chunk of code as “Yahoo_Map_Maker” and run it. It should say “Getting map…” for a few seconds and then show a map of Battery Park.
And then deploy it onto your N95. The same thing should happen.
Did it? Great!
Adding the GPS
Now we’re going to play a game. To test your Mobile Processing chops, can you add the GPS-grabbing code into this “Yahoo_Map_Maker” code so that instead of being fixed on Battery Park it shows a map of your GPS location?
You’ll need to do a few things to make it happen:
• You’ll have to import the mLocation library. Remember how to do that?
• You’ll then need to set up an array and grab the GPS coordinates and put them into that array.
• Then you’ll need to use those coordinates to grab the map from Yahoo! instead of the fixed coordinates.
Three steps. Try it out.
Did you get it to work?
Here’s how I did it:
import mjs.processing.mobile.mlocation.*;
PClient clientXML;
PClient clientMap;
PRequest requestXML;
PRequest requestMap;
PFont font;
PImage mapImage;
String xml;
String mapURL;
double[] coords = new double[2];
// Battery Park, NY
double latitude = 40.703717;
double longitude = -74.016094;
void setup() {
clientXML = new PClient(this, “local.yahooapis.com”);
clientMap = new PClient(this, “gws.maps.yahoo.com”);
font = loadFont();
textFont(font);
noLoop();
MLocation.location(coords);
latitude = coords[0];
longitude = coords[1];
}
void draw() {
background(0);
fill(255);
if( mapImage != null ) {
image(mapImage, width/2-mapImage.width/2, height/2- mapImage.height/2);
} else {
text(”Getting map…”, 4, 4, width - 8, height - 8);
requestXML = clientXML.GET(”/MapsService/V1/mapImage?appid=
CHANGETHIS&latitude=” + latitude + “&longitude=” + longitude + “&zoom=2&image_height=” + height + “&image_width=” + width);
}
}
void libraryEvent(Object library, int event, Object data) {
if (library == requestXML) {
if (event == PRequest.EVENT_CONNECTED) {
requestXML.readBytes();
} else if (event == PRequest.EVENT_DONE) {
xml = new String((byte[]) data);
int start = xml.indexOf(”instance\”>”) + 10;
int end = xml.indexOf(”");
mapURL = xml.substring(start, end);
requestXML.close();
requestMap = clientMap.GET(mapURL.substring(25));
}
}
if (library == requestMap) {
if (event == PRequest.EVENT_CONNECTED) {
requestMap.readBytes();
} else if (event == PRequest.EVENT_DONE) {
mapImage = loadImage((byte[]) data);
requestMap.close();
redraw();
}
}
}
Right at the top I imported the mLocation library.
Then I set up my “coords” array:
double[] coords = new double[2];
Easy.
Then I queried the GPS with mLocation and put the results in the more easily readable “latitude” and “longitude” variables:
MLocation.location(coords);
latitude = coords[0];
longitude = coords[1];
Deploy this and run it. It should bring up a map of the area where you are. Beware: It might take up to a minute for the GPS to connect, and then you’ll have to wait a few more seconds for the map to download. Be a bit patient. If it doesn’t work, quit it and try reloading.
So there you have it.
I can’t wait to see what kind of amazing mapping applications you make with this. Please share in the comments section!
12:00 AM
12.22.07
12:00 AM
12.23.07
12:00 AM
01.03.08
12:00 AM
01.06.08
12:00 AM
01.08.08
12:00 AM
01.15.08
12:00 AM
01.17.08
12:00 AM
01.22.08
12:00 AM
05.09.08
12:00 AM
06.02.08
2:27 AM
07.17.08
12:30 PM
07.27.08