Lecture 17 - Google Maps

In this lecture, we learn how to incorporate Google Maps into applications - this is very cool. We have all used Google Maps on laptop browsers and smartphones but only as user up until now.

We will first learn how to create a map activity. Then through two simple demos apps we get a sense of the main programming features needed to construct and control maps. I even named a demo app after the city recently described by New York Times journalist Neil MacFarquhar as a "drab industrial city". Cheers Neil - I was born and brought up in the hip, bustling, vibrant town of Coventry. You clearly now "darb" when you see it Neil. I digress. Now we transition from one Android aka Neil to another.

What this lecture will teach you

Demo projects

The demo code used in this lecture include:

The Coventry demo is take from here: detect MarkerClick and add Polyline The app detects long click on map and adds a marker. Lines can be drawn between markers using polylines.

These two apps will provide the necessary background to implement maps for MyRuns4.

Resources

Some excellent references.

How to create a Google Maps Activity

Back in the times when we use Eclipse and ADT, the set up procedures for a working Google Map environment on your IDE is very complicated. You probably cannot get it right for the first time. In Android Studio, most of the procedures are simplified into two basic steps: add a Google Maps Activity for your project, and generate a unique API key for your project.

The detailed process of creating a new Android application that uses the Google Maps Android API v2 requires several steps. Note that this guide explains all the automatically-generated codes by Android Studio, so it will be helpful for you to understand the meanings of them. Below we only illustrate the Android Studio way to do this - it is much faster to set up but since it is an automatic procedure, we recommend you to go back to the official guide to go over things to fully understand the whole picture.

Many of the details outlined in this section will only have to be performed once, but some of the information will be a handy reference for future applications. The overall process of adding a Google Map to an Android application is as follows:

STEP 1: Add a Google Maps Activity to your project

  1. As shown in figures below, you can either create a Google Maps Activity project from scratch (left) or add a Google Maps Activity (right) to your existing projects (e.g, MyRuns).
  1. Have a first look of the generated MapsActivity. It extends itself from a FragmentActivity, containing a GoogleMap instance.

STEP 2: Obtain an API key for Google Maps v2.0

  1. Go to google_maps_api.xml, you will find a generated url in the comments. This url is generated using the unique SHA-1 key of your current computer and the package name of the app. We also notice that there is a placeholder specifying the "google_maps_key". We need to get the key from the Google API Console and fill it in.
  1. Copy this link to your browser and hit Enter, you will be directed to a page on the API console as shown below:
  1. Hit "Continue" and “Create API key” to proceed. Wait a few seconds - your API key is being generated.
  1. Once the server is ready, you will be presented with the page like below. Copy the API Key back to the key placeholder in the xml file.
  1. There are a number of meta-data and uses-permissions added to the Manifest file by Android Studio. Once you have the API key you added to google_maps_api.xml, you will find the meta-data for the api key in Manifest is updated as well (shown below). Now you are done with the setup for a Google Maps Activity.

I _Am_Here -- a tracking app

The first application we look at this is an extension of the applications we developed for the lecture on the LocationManger. As shown in the image below the app lists:

The cool thing about this app is that as you move around it will update your position on the map. It tracks you. There is little control over the app. You can move the map around or use the simple zoon in / zoom out buttons on the map -- that is about it.

Let's discuss the code. Note, in the code examples below we snip some of the code that we have already discussed in the pervious lecture. You can look at the demo app source code to see the complete source code.

Much of the structure of the code is familiar now.

Set up Google Maps in onCreate()

The code first gets a reference to a GoogleMap using getSupportFragmentManager() on SupportMapFragment set up in layout/activity_main.xml, as shown below in the layout file

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/locinfo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools" 
            android:layout_width="match_parent"
            android:layout_height="match_parent" 
            android:id="@+id/map" tools:context=".MainActivity"
            android:name="com.google.android.gms.maps.SupportMapFragment" />

</LinearLayout>

The getMap() method renders the Google Map returned from the server into the SupportMapFragment in layout. The type of map is then set to normal.

There are a number of types of maps that can be selected:

Change the type of the map in your code and look at the map rendered.

   mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);

After the map type is set we get the current location and set a marker at that location and zooms in. The location manager sets up the time and distance parameters as well as the call back listener for location updates:

locationManager.requestLocationUpdates(provider, 2000, 10,
                                           locationListener);

We discussed these call backs in the last lecture. So check that out again if you need to. The helper function then gets called to update the map if necessary.

public class WhereAmI extends Activity {

    public GoogleMap mMap;
    public Marker whereAmI;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    setUpMapIfNeeded();

    LocationManager locationManager;
    String svcName= Context.LOCATION_SERVICE;
    locationManager = (LocationManager)getSystemService(svcName);

    [Code snippet]
    ........
    ........
    ........

    Location l = locationManager.getLastKnownLocation(provider);
    
    LatLng latlng=fromLocationToLatLng(l);
        
    whereAmI=mMap.addMarker(new MarkerOptions().position(latlng).icon(BitmapDescriptorFactory.defaultMarker(
             BitmapDescriptorFactory.HUE_GREEN)));
    // Zoom in
    mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latlng,
            17));
    
    updateWithNewLocation(l);

    locationManager.requestLocationUpdates(provider, 2000, 10,
                                           locationListener);
  }

  public static LatLng fromLocationToLatLng(Location location){
        return new LatLng(location.getLatitude(), location.getLongitude());
        
  }

User Tracking

Each time the callback onLocationChanged() is called the map is updated simply by calling the helper function discussed below. There is no action for the other callbacks in this code -- there really should be.

  private final LocationListener locationListener = new LocationListener() {
    public void onLocationChanged (Location location) {
      updateWithNewLocation(location);
    }

    public void onProviderDisabled(String provider) {}
    public void onProviderEnabled(String provider) {}
    public void onStatusChanged(String provider, int status, 
                                Bundle extras) {}
  };

Helper for Tracking

The helper function simply has the current location passed to it. It first removed the current marker and redraws a new marker at the new location.

 private void updateWithNewLocation(Location location) {
    TextView myLocationText;
    myLocationText = (TextView)findViewById(R.id.myLocationText);
      
    String latLongString = "No location found";
    String addressString = "No address found";
    
    if (location != null) {
      // Update the map location.
      
      LatLng latlng=fromLocationToLatLng(location);
      
      mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latlng,
            17));


      if(whereAmI!=null)
          whereAmI.remove();
      
      whereAmI=mMap.addMarker(new MarkerOptions().position(latlng).icon(BitmapDescriptorFactory.defaultMarker(
             BitmapDescriptorFactory.HUE_GREEN)).title("Here I Am."));
      
    [Snip code]
    ........
    ........
    ........

    
    }

Coventry Demo

The Coventry app allows the user to interact with the map by placing markers on the map and then connecting up the markers with polylines drawn on the map. A polyline is a list of points, where line segments are drawn between consecutive points. The app detects long clicks on map and adds a marker. Lines can be drawn between markers using polylines. To do this the use clicks (do not long click) on a marker then moves to another marker and clicks. To remove the markers and lines click on the map.

In the image below the user has long clicked on three places on the map creating three markers. Then the user has clicked (just normal short click) on each point and the lines are drawn constructing a triangle around the vibrant metropolis of Coventry, England.

onCreate()

public class MainActivity extends Activity 
    implements OnMapClickListener, OnMapLongClickListener, OnMarkerClickListener{
    
    private GoogleMap myMap;
    
    Location myLocation;
    TextView tvLocInfo;
    
    boolean markerClicked;
    PolylineOptions rectOptions;
    Polyline polyline;

    static final LatLng COVENTRY = new LatLng(52.4081, -1.5106);
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setUpMapIfNeeded();

        tvLocInfo = (TextView)findViewById(R.id.locinfo);
    }

Adding markers and drawing lines

The user can move the map around and zoom in/out as they wish. The callbacks set up in onCreate() are shown below:

Let's look at the code more below.

    @Override
    public void onMapClick(LatLng point) {
        tvLocInfo.setText(point.toString());
        myMap.animateCamera(CameraUpdateFactory.newLatLng(point));
        
        markerClicked = false;
    }

    @Override
    public void onMapLongClick(LatLng point){
        tvLocInfo.setText("New marker added@" + point.toString());
        myMap.addMarker(new MarkerOptions().position(point).title(point.toString()));
        
        markerClicked = false;
    }

    @Override
    public boolean onMarkerClick(Marker marker){
        
        if(markerClicked){
            
            if(polyline != null){
                polyline.remove();
                polyline = null;
            }
            
            rectOptions.add(marker.getPosition());
            rectOptions.color(Color.RED);
            polyline = myMap.addPolyline(rectOptions);
        }else{
            if(polyline != null){
                polyline.remove();
                polyline = null;
            }
            
            rectOptions = new PolylineOptions().add(marker.getPosition());
            markerClicked = true;
        }
        
        return true;
    }

}

The logic for onMarkerClick() is straightforward. First time onMarkerClick() is called it will call new PolylineOptions().add(marker.getPosition()) to add the marker to the polyline. Next time it is called it will add the second marker --rectOptions.add(marker.getPosition()) -- and then draw the line. Each time a new marker is added (via a long click) and a line drawn -- the complete set of lines are redrawn starting at the first marker added to the rectOptions (i.e., the ployline). If the user clicks on the map (not the markers) and then clicks on any marker or does a long click to create a new marker then the line or polyline is remove - that is, the line between the markers is cleared.