11. Using Location-Based Services (LBS) APIs

Whether for safety or for convenience, location-based features on cell phones are mostly standard these days. As such, incorporating location information, navigation, and mapping features into your project can make your application much more robust.

In this chapter, you learn how to leverage location-based services available within the Android SDK. You learn how to determine the location of the handset using a particular device hardware provider, such as a built-in Global Positioning Systems (GPS) unit. You also learn how to translate raw location coordinates into descriptive location names—and how to do the reverse. Finally, we explore a couple of different methods for mapping and utilities that work with the maps.

Using Global Positioning Services (GPS)

The Android Software Development Kit (SDK) provides means for accessing location via a built-in GPS device, when such hardware is available. If a GPS device isn’t available, the Application Programming Interfaces (API) also provides for alternate location providers. These other providers might have advantages and disadvantages in terms of power use, speed, and accuracy of reporting.

Finding Your Location

To determine device location, you need to perform a few steps and make some choices. The following list summarizes this process:

  1. Retrieve an instance of the LocationManager using a call to the getSystemService() method using the LOCATION_SERVICE.
  2. Add an appropriate permission to the AndroidManifest.xml file, depending on what type of location information the application needs.
  3. Choose a provider using either the getAllProviders() method or the getBestProvider() method.
  4. Implement a LocationListener class.
  5. Call the requestLocationUpdates() method with the chosen provider and the LocationListener object to start receiving location information.

Specific permissions are not needed to retrieve an instance of the LocationManager object. Instead, the permissions determine the available providers. The following code retrieves an instance of the LocationManager object:

image


The following block of XML provides the application with both coarse and fine location permissions when added within the AndroidManifest.xml permissions file:

image


Now that the application has permissions to use location information and the LocationManager object is valid, we must determine what provider to use for location information. The following code configures a Criteria object and requests the provider based on this information:

image


The setAccuracy() method can take values for ACCURACY_COARSE and ACCURACY_FINE that can be used (along with the appropriate permissions) to request a provider that the application has permissions to use. The setPowerRequirement() method can be used to find a provider that fits certain power use requirements, such as POWER_HIGH or POWER_LOW. The Criteria object also enables us to specify if the provider can incur a monetary cost to the user, whether altitude is needed, and some other details. If the application has specific requirements, this is where they can be set. However, setting these criteria doesn’t imply that the provider will be available to the user. Some flexibility might be required to allow use on a broad range of devices. A Boolean parameter of the getBestProvider() method enables the application to ask for only enabled providers.

Using the provider returned by the getBestProvider() method, the application can request the location. Before doing so, however, the application needs to provide an implementation of LocationListener. The LocationListener object consists of four methods: two tell the application if the provider has been disabled or enabled; one gives the status about the provider (such as the number of satellites the GPS receiver can see); and the last tells the application location information. The following is a sample implementation for the last method, the onLocationChanged()method:

image


The onLocationChanged() method receives a Location object with the most recent location information from the chosen provider. In this example, the application merely prints out the location, including the altitude, which might or might not be returned by the provider. Then, it uses a utility method of the Location object, distanceTo(), to calculate how far the handset has moved since the last time onLocationChanged() was called.

It is up to the application to determine how to use this location information. The application might want to turn the location information into an address, display the location on an embedded map, or launch the built-in map application centered at the location.

Locating Your Emulator

The Android emulator can simulate location-based services, but as you would expect, it does not have any “underlying hardware” to get a real satellite fix. The Android SDK provides a means to simulate location data with the use of a single location point, GPX file, or KML file. This works only with the emulator, not the physical handset, but it can be useful for testing your location-based application.

For more information on this, see Appendix A, “The Android Emulator Quick-Start Guide.”

Geocoding Locations

Determining the latitude and longitude is useful for precise location, tracking, and measurements; however, it’s not usually descriptive to users. The Android SDK provides some helper methods to turn raw location data into addresses and descriptive place names. These methods can also work in reverse, turning place names or addresses into raw location coordinates.

The Geocoder object can be used without any special permissions. The following block of code demonstrates using the Geocoder object to get the location names of a Location object passed in to the onLocationChanged() method of a LocationListener:

image


You can extract information from the results of the call to the getFromLocation() method in two ways, both of which are demonstrated. Note that a particular location might have multiple Address results in the form of a List<Address> object. Typically, the first Address is the most detailed, and the subsequent Address objects have less detail and describe a broader region.

The first method is to query for specific information, such as by using the getFeatureName() method or the getLocality() method. These methods are not guaranteed to return useful information for all locations. They are useful, though, when you know you need only a specific piece of general information, such as the country.

The second method for querying information is by “address lines.” This is generally used for displaying the “address” of a location to the user. It might also be useful to use the location in directions and in other cases where a street address is desired. That said, the addresses returned might not be complete. Simply call the getAddressLine() method and iterate through the values returned by getMaxAddressLineIndex(). Figure 11.1 shows a sample location with three resulting addresses.

Figure 11.1. Image showing location geocoded to three “addresses.”

image

The Geocoder object also supports using named locations or address lines to generate latitude and longitude information. The input is forgiving and returns reasonable results in most cases. For instance, all the following returns valid and correct results, “Eiffel Tower,” “London, UK,” “Iceland,” “BOS,” “Yellowstone,” and “1600 Pennsylvania Ave, DC.”

The following code demonstrates a button handler for computing location data based on user input of this kind:

image


The result of the call to the getFromLocationName() method is a List of Address objects, much like the previous example. Figure 11.2 shows the results for entering “Eiffel Tower.”

Figure 11.2. The results for geocoding the term Eiffel Tower.

image

We have never actually seen more than one result, even when entering ambiguous place names (such as “Dublin”). However, this behavior is not guaranteed. Providing a picker for the user to choose the best location would certainly enhance the application. Another good way to confirm with the user that they entered the correct location is to map it. We now discuss a couple of different methods for mapping locations using Google Maps.

Mapping Locations

The Android SDK provides two different methods to show a location with Google Maps. The first method is to use a location Uri to launch the built-in Google Maps application with the specified location. The second method is to use a MapView embedded within your application to display the map location.

Mapping Intents

In the previous section, we demonstrated how to determine the latitude and longitude for a place name. Now we map the location using the built-in maps application. The following block of code demonstrates how to perform this:

image


The first task is to create a String that conforms to the URI handled by the mapping application. In this case, it’s “geo:” followed by the latitude and longitude. This URI is then used to create a new Uri object for creating a new ACTION_VIEW Intent. Finally, we call the startActivity() method. If the latitude and longitude are valid, such as the location for the Parthenon entered in the previous example, the screen would look like Figure 11.3.

Figure 11.3. The resulting map for geocoding the term “Parthenon” and launching a geo URI.

image

Using this method of mapping launches the user into a built-in mapping application, in this case Google Maps. If the application does not want to bother with the details of a full mapping application or does not need to provide any further control over the map, this is a fast-and-easy method to use. Users will typically be accustomed to the controls of the mapping application on their handset, too.

Mapping Views

Sometimes, though, we want to have the map integrated into our application for a more seamless user experience. Let’s add a small map to our geocoding example to show the location immediately to the users when they enter a place name.

The following block of XML shows the change needed within the layout file to include a widget called the MapView:

image


As you might have already noticed, the MapView XML is a little different. First, the tag name is the fully qualified name. And second, an apiKey attribute is needed. We get to the key in a moment.

The AndroidManifest.xml file also needs to be modified to allow for using the MapView with Google Maps. Here are the two changes needed:

image


Both of these permission lines are required. The MapView object specifically requires the INTERNET permission and its library must be reference explicitly. Otherwise, an error will occur.

Finally, a MapView can be used only within a MapActivity. Accessing a MapView from outside a MapActivity results in an error. The MapActivity is similar to a normal Activity, but it requires implementing the isRouteDisplayed() method. This method must return true if a route will be displayed. Otherwise, false must be returned. Here is the default implementation for when no route is displayed:

image


Now the application can use the MapView to display locations to the user. The following block of code demonstrates retrieval of a MapController object, which is used to control the location that the MapView displays:

image


These lines of code set the display to show the satellite view, which is visually interesting. The MapController object then sets the zoom level of the map. Larger values are zoomed in farther, with 1 zoomed all the way out. The given value, 17, usually shows a few city blocks, but there are some areas where even this is too close for the data available. In a moment we talk about how to easily give control of this to the user.

Building on the previous example, the following lines of code are added to the button handler for geocoding a place name:

image


In this case, we create a new GeoPoint to use with the animateTo() method. A GeoPoint object uses microdegrees, so we must multiply the result of the geocoding by 1E6 (1,000,000 or one million). The animateTo() method smoothly animates the MapView to the new location. How much of the interim mapping data displays depends on the speed of the Internet connection and what mode the MapView is in. The setCenter() method can set the center of the map.

Finally, this is almost enough to test the results. However, there is one last thing that needs to be taken care of. You need to get a Google Maps API Key from Google to use its API and mapping services.

Getting Your Debug API Key

To use a MapView within your applications, you must obtain a Google Maps API Key from Google. The key is generated from an MD5 fingerprint of a certificate that you use to sign your applications.

For production distribution, you need to follow these steps, substituting your release distribution signing certificate. You can read more about this in Chapter 20, “Selling Your Android Application.” For testing purposes, you can use the debug certificate that is created by the Android SDK for your use.

The following steps need to be performed to generate the appropriate API key:

  1. Generate an MD5 fingerprint for your debug certificate.
  2. Sign in to http://code.google.com/android/maps-api-signup.html with a Google Account.
  3. Accept the Terms of Service.
  4. Paste in the fingerprint from step 1.
  5. Save the Android Maps API key presented on the next screen.

The first step is performed on your development machine. Locate the debug certificate used by the Android SDK. On all platforms, the filename is debug.keystore by default. If you use Eclipse, the location of the file is listed under the Android Build preferences. Using this file, you then need to execute the following command (make sure the Java tools are in your path):

keytool -list -keystore /path/to/debug.keystore


The result is the fingerprint that you must paste into the form on step 4. Read the terms of service carefully before proceeding. Although the terms allow many types of applications, you need to make sure your application will be allowed and that your anticipated usage will be acceptable to Google.

When you have successfully completed the steps to get your key, you can then reference your map key in the Layout file definition for the MapView you use. Now, when you execute the code, you should be presented with a screen that looks like Figure 11.4.

Figure 11.4. MapView results for geocoding the term “Sydney Opera House.”

image

Tip

If you work on multiple development machines or work as part of a team, you need to have an API key for everyone’s debug certificate. Alternating, the debug certificate from one machine can be copied to other machines so that the signing and check against the Android Maps API key is successful. This can save time by not having to modify the code or layout files for each developer on the team.

Panning the Map View

Sometimes the locations returned either do not show the exact location that the user wanted or the user might want to determine where in the world they are by exploring the map a bit. One way to do this is through panning the map. Luckily, this is as easy as enabling clicking from within the layout file:

image


Now, if users were to search for “The Great Pyramid,” they could then pan south a tad and see the results, as shown in Figure 11.5.

Figure 11.5. Results for “The Great Pyramid” on the left, panned south by about a screen on the right.

image

Zooming the Map View

Other times, panning around won’t help the users. They might want to zoom in or out from the same location. Our application does not have to reimplement the zoom controls, though. They are provided in the Android SDK 1.5 through a single call.

map.setBuiltInZoomControls(true);


When the user clicks on the map, the zoom controls fade in to view and are functional, as shown in Figure 11.6.

Figure 11.6. On the right, you see a bird’s eye view of the town of Wilmington, but zoom in and you see The Long Man of Wilmington as shown on the left.

image

Marking the Spot

Now that users can pan and zoom around, they might lose their position. Sure, they can just search again. Wouldn’t it be more interesting, though, to mark the spot for them? The Android SDK provides a few different ways to do this. One way is to use the MapView as a container for an arbitrary View object that can be assigned using a GeoPoint instead of typical screen or View coordinates. Another way is to use ItemizedOverlay, which is especially useful if you have more than one place to mark. Finally, you can manually draw items over the map using the Overlay and implement the onDraw() method.

For the place name finder example, we use the first method. Assuming you have a suitable map marker as a drawable resource, the following code demonstrates how to do this:

image


The MapView layout parameters enable you to set a GeoPoint. Doing this allows the added View to stay put at a geographic location and pan with the map, as shown in Figure 11.7.

Figure 11.7. The Kremlin at the top left of the marker (paw print in a circle).

image

Keep in mind that the added View sticks around as long as the MapView does. If the application needs to present multiple locations to the user, though, there is a simpler way. Just use the ItemizedOverlay object.

In this example, a static ItemizedOverlay will be created to represent the chain of backpacker huts in the White Mountains along the Appalachian Trail:

image


To do this, we provide implementations for each of the required methods of ItemizedOverlay<OverlayItem>. First, we define the constructor:

image


The Drawable passed in is one that we define later in the onCreate() method of MapActivity. The system does not provide a default marker. The call to the boundCenterBottom() method is made so that the shadow will be cast from the bottom of the marker, which is a more natural look. The default shadow is from the top. If, however, we’d rather turn off the shadow completely, the draw() method could be overridden, as follows:

image


Finally, within the constructor we call the populate() method. This should be done as soon as the location data is available. Because we have it statically compiled into the application, we call it before returning. The populate() method calls our implementation of the createItem() method for as many items as we defined in our implementation of the size() method. Here is the implementation of our createItem() method, along with a small array of hut locations, in no particular order:

image


In the array, we’ve multiplied all the location values by one million so that they are in microdegrees, as required by the GeoPoint object. Within the createItem() method, the location array is indexed with the passed in value. Neither of the two text fields, Title and Snippet, are used at this time, so they are set to null. The maximum index value is determined by the size() method, which, in this case, merely has to return the length of the array:

image


The necessary ItemizedOverlay<OverlayItem> class is now implemented. Next, the application needs to tell the MapView about it. The following code demonstrates how to do this in the onCreate() method of our MapActivity:

image


First, the Drawable is retrieved from the resources. We call setBounds() on it to force it to draw correctly as part of an ItemizedOverlay. Without this, the markers will not draw. Next, we instantiate the HutsItemizedOverlay object. The OverlayItems in it need to be added to the ones that might already exist within the MapView. The getOverlays() method of MapView returns a list of the current Overlay objects. Calling the add() method on this list inserts our new ones for each hut. Finally, the zoom controls are added to the MapView so that the user can zoom in and out. After launching this application and zooming in on New Hampshire, the user should see a screen like Figure 11.8.

Figure 11.8. A map with markers at each of the Appalachian Mountain Huts of New Hampshire.

image

Forcing the user to pan and zoom to the location of the huts is not user-friendly. Two of the utility methods that the ItemizedOverlay<OverlayItem> class provides returns values for the span of the location of the items. Combining this functionality with an override to the default behavior of the getCenter() method, which normally returns the location of the first item, enables the map to start to draw at a convenient zoom level covering all the huts. This block of code can be added to the onCreate() method to do just that:

image


The getCenter() method computes the average latitude and the average longitude across all the given hut locations. A central point could be provided, or the first item could be placed near the center of all the points requiring no override of the getCenter() method.

Doing More with Location-Based Services

You have been introduced to a number of different location tools provided on Android; however, you should be aware of several more.

The LocationManager supports Proximity Alerts, which are alerts that trigger a PendingIntent when the handset comes within some distance of a location. This can be useful for warning the user of an upcoming turn in directions, for scavenger hunts, or help in geocaching.

You saw how to do ItemizedOverlays. In general, you can assign your own Overlays to draw custom objects and Views on the given Canvas. This is useful for drawing pop-up information for locations, putting logos over the map that don’t move with the map, or putting hints for scavenger hunts over the map. This functionality is similar to displaying photos at a given location, which are often provided on Google Maps at famous locations.

Summary

The Android SDK, with Google Maps support available to applications that register for a key, can be used to enhance Android applications with location-rich information. Some applications want to build in seamless map support, whereas others might just launch the built-in map application for the user to leverage. Developers can add to the information provided on the map by using various types of overlays to include even more information to the user. The opportunities for using location-based services to improve Android applications are only just beginning to be explored.

References and More Information

Google Maps API Key: http://code.google.com/android/add-ons/google-apis/mapkey.html

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.103.227