Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

Android SDK: Build a Mall Finder App - Points of Interest

by
Gift

Want a free year on Tuts+ (worth $180)? Start an InMotion Hosting plan for $3.49/mo.

This post is part of a series called Build a Mall Finder App.
Android SDK: Build a Mall Finder App - Mapview & Location

This tutorial will explore how to use the Google Maps API to overlay points of interest (POI) and use the available Location Based Services on your handset to show your position relative to the POI locations available. In this case, we shall be using Shopping Malls as the POI.

In part one of this series, we covered how to use the MapView and obtain your current location by implementing the location listener. In this lesson, we will be expanding on what was done in the first tutorial and adding the overlays to show your current position and the location of some points of interest (POI). We will do this by using an external library that has already been developed by Jeff Gifelt. If you have not completed the first tutorial, I strongly advise you do so before starting this one. However, if you decide not to, you can use the source files included at the end of the last tutorial as the starting point for this one.

You should note that in the previous lesson we already covered how to get your current location. In this lesson we will be hard coding our location to Florida University (this is only for the sake of this tutorial, so we can show malls in that area).

Since we will be continuing from the last tutorial, you should go ahead and open that project.


Step 1: Displaying Location Information

The last step we completed previously was to display our current location, but let's expand a bit on this and actually display some information about our current position, such as latitude and longitude.

This information will be displayed as white text on a semi-transparent black background near the top of the MapView.

Open the layout file (main.xml), which is located at MallFinder > res > layout > main.xml

Add the following code after the MapView closing tag, but before the closing tag of the FrameLayout:

<LinearLayout
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:id="@+id/infoLinearLayout"
    android:clickable="true"
    android:onClick="centerToCurrentLocation">
    <TableLayout 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:background="#97000000"
        android:padding="7sp">
        <TableRow>
            <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/latitude"
            android:id="@+id/latitudeText"
            android:textColor="#FFFFFF">
            </TextView>
            <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/longitude"
            android:id="@+id/longitudeText"
            android:textColor="#FFFFFF">
            </TextView>
        </TableRow>
       	<TableRow>
            <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/accuracy"
            android:id="@+id/accuracyText"
            android:textColor="#FFFFFF">
            </TextView>
            <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/provider"
            android:id="@+id/providerText"
            android:textColor="#FFFFFF">
            </TextView>
        </TableRow>
    </TableLayout>
</LinearLayout>

You may have noticed that an onClick method was specified for the LinearLayout, this method will be used to center the MapView around the user's current location when the semi transparent LinearLayout is tapped. This will be useful if the user decides to scroll around on the MapView and would like to quickly return their current location.

In the file above, a couple of strings were referenced (the lines that begin with android:text="@string..."), but these strings were not created as yet, so let's do that now.

Open the strings.xml file located at MallFinder > res > values > strings.xml and add the following lines of code.

<string name="latitude">Latitude : </string>
<string name="longitude">Longitude : </string>
<string name="accuracy">Accuracy : </string>
<string name="provider">Provider : </string>

Now that we have updated the UI, let's update the MallFinderActivity.java class code to display our location coordinates.

Open the MallFinderActivity.java file and add the following line to the getLastLocation method:

((TextView)findViewById(R.id.providerText)).setText("Provider :" + getBestProvider());

Update the setCurrentLocation method to include the following lines:

((TextView)findViewById(R.id.latitudeText)).setText("Latitude : " + String.valueOf((int)(location.getLatitude()*1E6)));
((TextView)findViewById(R.id.longitudeText)).setText("Longitude : " +  String.valueOf((int)(location.getLongitude()*1E6)));
((TextView)findViewById(R.id.accuracyText)).setText("Accuracy : " + String.valueOf(location.getAccuracy()) + " m");

Lastly, add the following method:

public void centerToCurrentLocation(View view){
    	animateToCurrentLocation();
}

If you run the app now, you should see your current latitude, location, provider, and accuracy displayed over the MapView.
Note: if this is the first time running the app the latitude, longitude, and other locations may be blank until your phone can detect your current location. This can take some time depending on the provider being used.

Image with Current Location

Step 2: Using an External Library

We are about to begin adding the overlays onto our MapView to show our current position as well as the positions of the various places of interest. In order to this we will be using a customized BalloonItemizedOverlay class which was developed by Jeff Gifelt and is located at https://github.com/jgilfelt/android-mapviewballoons. You should note that Android has its own ItemizedOverlay class which can be used to overlay graphics onto a MapView, I chose this class over the ItemizedOverlay class because it provides a popup balloon feature when an overlay graphic is pressed, a feature which the ItemizedOverlay class does not have by default. This will also give us a chance to see how to setup and include external library projects in our own projects.

Let's start by downloading the file. Open your web browser and navigate to https://github.com/jgilfelt/android-mapviewballoons/downloads and click on the "Download as zip" button.

After downloading, unzip the file and note the file location.

In Eclipse, Navigate to File > Import. In the new window, select "Existing Projects into Workspace” and then click next.

Importing External Library

For the "Select root directory field", browse to the folder you unzipped the BalloonItemizedOverlay into and select it. In the Projects area only the android-mapviewsballoon project is required. However, you are free to select both projects if you would like the example project included.
Make sure the Copy Projects into workspace checkbox is selected. Click finish.

Importing External Library Step 2

You should now see the android-mapviewballoons project in the package explorer window.

Right click on the android-mapviewballoons project and select properties. A new window will pop up. On the left column, under the “type filter text” text box, click on Android on the right hand side of the window ensure the “Is Library” checkbox is selected, if it is not then select it and click Apply and then OK.

Importing External Library Step 3

You can now use this library in your existing and new projects as we are going to do now.

We are going to have our MallFinder Project, use the library that we included above. To do this, right click on the MallFinder Project in the Package Explorer Window in Eclipse, then Properties, and select Android on the left column as you did in the step above. In the right column, look for the Library area (the same area in the previous step where you selected “Is Library”), but this time click on the Add button. A new Project Selection Window will pop up. Select the android-mapviewballoons and then click OK.

Importing External Library Step 4

You should see an entry with the reference to the project. Select android-mapballoons and click Apply. Next click OK.


Step 3: Creating the Overlay Class

We are now going to create a MallOverlay class, which extends the BalloonItemizedOverlay class. Go to MallFinder > src > com.shawnbe.mallfinder. Right Click on the com.shawnbe.mallfinder in the Package Explorer window. Then select New > Class. Name the class MallOverlay, and then click Finish.

Creating Overlay Class

We need to update the MallOverlay class to extend BalloonItemizedOverlay.

Do this by updating the line:

public class MallOverlay {

to:

public class MallOverlay extends BalloonItemizedOverlay<OverlayItem> {

We also need to import a few items. To do this, add the following lines at the top of the MallOverlay.java class:

import android.graphics.drawable.Drawable;
import com.google.android.maps.MapView;
import com.google.android.maps.OverlayItem;
import com.readystatesoftware.mapviewballoons.BalloonItemizedOverlay;

Next, we will need to add a constructor and implement some of the required methods when extending the BalloonItemizedOverlay class:

Note: Eclipse allows us to easily fix errors and add unimplemented methods where applicable by notifying us of errors and suggesting fixes where possible.

For example, before we include the constructor or the needed methods you will notice that the word MallOverlay is underlined in red in the line

public class MallOverlay extends BalloonItemizedOverlay<OverlayItem> {

This indicates that there is an error. Place your cursor over the word MallOverlay and an information dialog will appear letting you know that you must define a contructor and there is one quick fix available.

Autofix Error

Click on the Add Constructor "MallOverlay(Drawable,MapView)" link and a skeleton constructor will be added for you.

Notice that the word MallOverlay is still underlined. Hover again and you will see this is because there are unimplemented methods. Click on the "add unimplemented methods" link and the two skeleton methods will be created for you.

It's very important that you understand what is causing your errors rather than just relying on this feature, but once you understand what may be causing errors in your code. This feature can save you a lot of time and effort by not having to type out each method.

The MallOverlay class should look like this


package com.shawnbe.mallfinder;

import android.graphics.drawable.Drawable;
import com.google.android.maps.MapView;
import com.google.android.maps.OverlayItem;
import com.readystatesoftware.mapviewballoons.BalloonItemizedOverlay;


public class MallOverlay extends BalloonItemizedOverlay<OverlayItem> {

	public MallOverlay(Drawable defaultMarker, MapView mapView) {
		super(defaultMarker, mapView);
		// TODO Auto-generated constructor stub
	}

	@Override
	protected OverlayItem createItem(int i) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int size() {
		// TODO Auto-generated method stub
		return 0;
	}

}

Now, we are going to add some functional code to the skeleton methods. Declare these three global variables:

private Context mContext;
private ArrayList<OverlayItem> malls = new ArrayList<OverlayItem>();
private Location currentLocation;

Update and add the methods in the MallOverlay class so they are the same as below:

public MallOverlay(Drawable defaultMarker, MapView mapView) {
	super(boundCenter(defaultMarker),mapView);
	boundCenter(defaultMarker);
	mContext = mapView.getContext();
}

@Override
protected OverlayItem createItem(int i) {
	// TODO Auto-generated method stub
	return malls.get(i);
}

@Override
	public int size() {
	// TODO Auto-generated method stub
	return malls.size();
}
	
public void addOverlay(OverlayItem overlay) {
    malls.add(overlay);
    populate();
}
	
@Override
protected boolean onBalloonTap(int index, OverlayItem item) {
	Toast.makeText(mContext, "Overlay Item " + index + " tapped!",
			Toast.LENGTH_LONG).show();
	return true;
}

We now have a class that for the most part handles the drawing of the overlays on to a MapView.


Step 4: Current Position Overlay

We will be using two pin graphics on the map overlay, a blue pin to show our current location and a red pin to show the surrounding malls.

My Location Icon
Mall Location Icon

Download these two graphics and place them in your drawable folder. Since I am dealing with an HDPI screen, I will use the Mallfinder > res > drawable-hdpi folder. You should note that when developing an application for public use since you have no idea what kind of pixel density the user's handset will have so you should create an appropriate graphic for each of the three densities. This will prevent your graphics from having to be stretched and shrunk as this can make your graphics look different than how you intended it to look.

We will create a method that takes our current position and overlay a drawable image.

Add the method below to the MallFinderActivity class:

public void drawCurrPositionOverlay(){
	List<Overlay> overlays = mapView.getOverlays();
	overlays.remove(currPos);
    Drawable marker = getResources().getDrawable(R.drawable.me);
    currPos = new MallOverlay(marker,mapView);
    if(currentPoint!=null){
	    OverlayItem overlayitem = new OverlayItem(currentPoint, "Me", "Here I am!");
	    currPos.addOverlay(overlayitem);
	    overlays.add(currPos);
        currPos.setCurrentLocation(currentLocation);
    }
}

Also, add the following line to the onCreate method after the getLastLocation method is called and also to the end of the setCurrentLocation method.

drawCurrPositionOverlay();

Run the application and you should see the blue pin showing your current location.

MapView Showing current Location

Step 5: Adding the Mall Marker Overlays

So far in our tutorial, we have shown you how to obtain your current location and mark it using an overlay item. However, for the sake of the rest of the tutorial, we will be hard coding your current location to the Florida International University (FIU). In our example, I will be using malls around the FIU area. Rather than having you scroll to that area every time, you launch the application we will placing you at FIU.

Let's do this now by opening the MallFinderActivity class and commenting on the first three lines of code in the setCurrentLocation method and adding these four lines of code after the commented area.

currentPoint = new GeoPoint(29647929,-82352486);
currentLocation = new Location("");
currentLocation.setLatitude(currentPoint.getLatitudeE6() / 1e6);
currentLocation.setLongitude(currentPoint.getLongitudeE6() / 1e6);

The four lines of code above should also be placed before the lines in the getLastLocation method:

if(currentLocation != null){
    	setCurrentLocation(currentLocation);
}

At this time, the complete setCurrentLocation method should be:

public void setCurrentLocation(Location location){
     /*	int currLatitude = (int) (location.getLatitude()*1E6);
    	int currLongitude = (int) (location.getLongitude()*1E6);
    	currentPoint = new GeoPoint(currLatitude,currLongitude); */ 
    	
    	/*========================================================================================
    	/*The Above Code displays your correct current location, but for the sake of the demo
    	I will be hard coding your current location to the University of Florida, to get your real
    	current location, comment or delete the line of code below and uncomment the code above. */
    	
    	currentPoint = new GeoPoint(29647929,-82352486);
    	currentLocation = new Location("");
    	currentLocation.setLatitude(currentPoint.getLatitudeE6() / 1e6);
    	currentLocation.setLongitude(currentPoint.getLongitudeE6() / 1e6);
    	
    	
    	((TextView)findViewById(R.id.latitudeText)).setText("Latitude : " + String.valueOf((int)(currentLocation.getLatitude()*1E6)));
    	((TextView)findViewById(R.id.longitudeText)).setText("Longitude : " +  String.valueOf((int)(currentLocation.getLongitude()*1E6)));
    	((TextView)findViewById(R.id.accuracyText)).setText("Accuracy : " + String.valueOf(location.getAccuracy()) + " m");
    	drawCurrPositionOverlay();
    }

and the getLastLocation method:

public void getLastLocation(){
    String provider = getBestProvider();
    currentLocation = locationManager.getLastKnownLocation(provider);
    	
    /*The next 4 lines are used to hardcode our location
     * If you wish to get your current location remember to
     * comment or remove them */
    	
    currentPoint = new GeoPoint(29647929,-82352486);
    currentLocation = new Location("");
    currentLocation.setLatitude(currentPoint.getLatitudeE6() / 1e6);
    currentLocation.setLongitude(currentPoint.getLongitudeE6() / 1e6);
    	
    if(currentLocation != null){
        setCurrentLocation(currentLocation);
    }
    else
    {
	    Toast.makeText(this, "Location not yet acquired", Toast.LENGTH_LONG).show();
    }
    ((TextView)findViewById(R.id.providerText)).setText("Provider :" + getBestProvider());    	
}

Add the following method to the MallFinderActivity class, this hardcodes some shopping areas that are nearby FIU.

public void drawMalls(){
	Drawable marker = getResources().getDrawable(R.drawable.malls);
 	MallOverlay mallsPos = new MallOverlay(marker,mapView);
    	GeoPoint[] mallCoords = new GeoPoint[6];
    	
    	//Load Some Random Coordinates in Miami, FL
    	mallCoords[0] = new GeoPoint(29656582,-82411151);//The Oaks Mall
    	mallCoords[1] = new GeoPoint(29649831,-82376347);//Creekside mall
    	mallCoords[2] = new GeoPoint(29674146,-8238905);//Millhopper Shopping Center
    	mallCoords[3] = new GeoPoint(29675078,-82322617);//Northside Shopping Center
    	mallCoords[4] = new GeoPoint(29677017,-82339761);//Gainesville Mall
    	mallCoords[5] = new GeoPoint(29663835,-82325599);//Gainesville Shopping Center    	
   	
    	
    	List<Overlay> overlays = mapView.getOverlays();
	OverlayItem overlayItem = new OverlayItem(mallCoords[0], "The Oaks Mall", "6419 W Newberry Rd, Gainesville, FL 32605");
	mallsPos.addOverlay(overlayItem);
	overlayItem = new OverlayItem(mallCoords[1], "Creekside Mall", "3501 Southwest 2nd Avenue, Gainesville, FL");
	mallsPos.addOverlay(overlayItem);
	overlayItem = new OverlayItem(mallCoords[2], "Millhopper Shopping Center", "NW 43rd St & NW 16th Blvd. Gainesville, FL");
	mallsPos.addOverlay(overlayItem);
	overlayItem = new OverlayItem(mallCoords[3], "Northside Shopping Center", "Gainesville, FL");
	mallsPos.addOverlay(overlayItem);
	overlayItem = new OverlayItem(mallCoords[4], "Gainesville Mall", "2624 Northwest 13th Street Gainesville, FL 32609-2834");
	mallsPos.addOverlay(overlayItem);
	overlayItem = new OverlayItem(mallCoords[5], "Gainesville Shopping Center", "1344 N Main St Gainesville, Florida 32601");
	mallsPos.addOverlay(overlayItem);
	overlays.add(mallsPos);	
		
	mallsPos.setCurrentLocation(currentLocation);
}

We need to call the above method when the page loads. To do this, add the following line after the drawCurrPositionOverlay() method in the onCreate method:

drawMalls();

Run the application and you current location should be FIU and you should also see 5 red markers representing the malls around that area:

Mapview showing mall locations

Step 6: Displaying Relevant Information

Currently when you tap on one of the markers it will give you some brief information such as the name and the address of the mall. However, let's add just a little more relevant information. We are going to display the distance to the mall when the user taps on the information balloon.

Open the MallOverlay.java file located at MallFinder > com.shawnbe.mallfinder > MallOverlay.java and add the method below. This method accepts a GeoPoint as an argument and returns a Location. In order to use the distanceTo method provided by Android we need to supply the two positions as location objects, our method below will convert a GeoPoint to a location object:

public Location convertGpToLoc(GeoPoint gp){
	Location convertedLocation = new Location("");
	convertedLocation.setLatitude(gp.getLatitudeE6() / 1e6);
	convertedLocation.setLongitude(gp.getLongitudeE6() / 1e6);
	return convertedLocation;
}

We now need to update the onBalloonTap method to calculate the distance between our location and the overlay that was tapped and display the information as a toast.

Update the onBalloonTap method to the following:

@Override
protected boolean onBalloonTap(int index, OverlayItem item) {
	String tmp = malls.get(index).getTitle();
	GeoPoint mallPoint = malls.get(index).getPoint();
	Location tmpLoc = convertGpToLoc(mallPoint);
	double distance = ((currentLocation).distanceTo(tmpLoc))*(0.000621371192);
	DecimalFormat df = new DecimalFormat("#.##");
	tmp = tmp + " is " + String.valueOf(df.format(distance)) + " miles away.";
	Toast.makeText(mContext,tmp,Toast.LENGTH_LONG).show();
	return true;
}

When you now tap on a marker, some basic information about the name and address will be displayed in a pop-up balloon. Now, if you tap on that balloon a toast will appear with your distance from the specified mall. Save your changes and run the application to try this out:

MapView displaying Location information

There it is a working MapView with overlays. Over the past two tutorials you have learned how to use the MapView object, register for an API key, get your current location, specify criteria for your preferred provider, use external libraries, animate to a position on the MapView, add overlays on various points on the map, and even add balloon pop-ups. I hope this tutorial was informative and gives you the knowledge needed to create your own location based application. Feel free to email me at shawn@shawnbe.com if something has not been explained properly and I will do my best to clarify, or if you have suggestions or general inquiries or comments feel free to share!

Advertisement