andbook!.pdf - Learning Android Get an anddev.org - Android-Shirt Back to index
anddev.org Header Logo
FAQ Search Top rated articles Browse Feeds anddev.org - Authors Contact Details Register Log in

Google Driving Directions - MapView overlayed

Goto page 1, 2, 3  Next
 
       anddev.org - Android Development Community | Android Tutorials | Index -> Map Applications
Author Message
plusminus
Site Admin


Joined: 14 Nov 2007
Posts: 1880
Location: Germany

PostPosted: Sat Feb 09, 2008 4:17 pm    Post subject: Google Driving Directions - MapView overlayed Reply with quote

Google Driving Directions - MapOverlayed
Part of the AndNav!-Application Exclamation


What is this: This tutorial shows how to use the Android built in access to the Google DrivingDirections API. The first step to create an actual Navigation-System.

Idea Designed/Tested with sdk-version: m5-rc14

Question Problems/Questions: post right below...

Difficulty: 2.5 of 5 Smile

What it will look like:
Screenshot was taken with sdk-version m3 (m5 looks similar)


Description: Creating a Navigation-System has never been so easy. Get the DrivingDirections from A to B with less then a handful of codelines.

0.) As usual lets start with the Layout. Its pretty simple, just a "Do it"-Button and a MapView:
Java:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >    
    <Button android:id="@+id/cmd_submit"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:text="Show me the Route!"/>    
     <view class="com.google.android.maps.MapView"
            android:id="@+id/myMapView"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"/>    
</LinearLayout>


1.) Now to the DrivingDirections this tutorial is actually about. The DrivingDirections which are very very close related to GoogleMaps are fully integrated into Android. You can find them in the package: "com.google.googlenav"-Package.
Java:
import com.google.googlenav.DrivingDirection;

It provides the DrivingDirection-Class with the following Constructor:
Java:
new DrivingDirection(MapPoint from, String from_string, MapPoint to, String to_string);

You can call it either with two MapPoints or with the Description of two places (in this case pass null to the MapPoints-Parameters). In this Tutorial we will be using two pre-defined gps-coordinates, from the Google-Headquarters to the next beach Rolling Eyes.

The request for the DrivingDirections will be fired on a Click to our Button we defined in xml.
Java:
          /* Submit one dummy search */
          findViewById(R.id.cmd_submit).setOnClickListener(new OnClickListener(){
               @Override
               public void onClick(View arg0) {
                    MapPoint mpFrom = new MapPoint(37423157, -122085008); // 37.423157,-122.085008
                    MapPoint mpTo = new MapPoint(37495999, -122457508); // 37.495999,-122.457508
                    MyDrivingDirectionsActivity.this.startFetchDirections(mpFrom,"", mpTo, "");
               }
          });

So, a click to that button will finally cause a field in our class to be set, which was initiated with null:
Java:
private DrivingDirection myDD = null;


2.) In the onCreate(...);-Function we applied a custom Overlay to our MapView using:
Java:
          /* Find the MapView we defined in the main.xml. */
          MapView mapViewFromXML = (MapView)this.findViewById(R.id.myMapView);
          /* Retrieve its OverlayController. */
          OverlayController myOC = mapViewFromXML.createOverlayController();
          /* Add a new instance of our fancy Overlay-Class to the MapView. */
          myOC.add(new MyMapDrivingDirectionsOverlay(this), true);


3.) This custom Overlay will draw the DrivingDirections-Route(if found) above the Map. It mainly contains just code in the onDraw()-Function which will draw the route in a fancy way, like it can be seen in the picture in the beginning. The path-drawing-code is the following:

Java:
               //...
                    /* Check to see if the route is too long. */
               if (!dd.routeTooLong()) {
                    /* Retrieve all (Map)Points of the route Found. */
                    MapPoint[] route = dd.getRoute();
                    
                    if(route == null)
                         return;
                    
                    /* Loop through all MapPoints returned. */
                    for (MapPoint current : route) {
                         /* Transform current MapPoint's Lat/Lng
                          * into corresponding point on canvas
                          * using the pixelCalculator. */

                         if(current != null){
                              MapPointToScreenCoords(current, screenCoords, pxC);
                              /* Add point to path. */
                              thePath.lineTo(screenCoords[0], screenCoords[1]);
                         }
                    }
               }
               this.pathPaint.setStyle(Paint.Style.STROKE);
               /* Draw the actual route to the canvas. */
               canvas.drawPath(thePath, this.pathPaint);
               //...


Idea Note: After having clicked the Button the DrivingDirections are not instantly drawn. You need to move the map at least one pixel, because we need to invalidate() the MapView once. This can also be accomplished using a Handler like shown in the The Pizza Timer-Tutorial.


The Full Source:
"/res/layout/main.xml":
Java:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >    
    <Button android:id="@+id/cmd_submit"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:text="Show me the Route!"/>    
     <view class="com.google.android.maps.MapView"
            android:id="@+id/myMapView"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"/>    
</LinearLayout>


"/src/your_package_structure/MyDrivingDirectionsActivity.java":
Java:
package org.anddev.android.drivingdirections;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;

import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
import com.google.android.maps.OverlayController;
import com.google.googlenav.DrivingDirection;
import com.google.googlenav.map.MapPoint;

public class MyDrivingDirectionsActivity extends MapActivity {

     private DrivingDirection myDD = null;
     private boolean foundDirections = false;

     /** Called when the activity is first created. */
     @Override
     public void onCreate(Bundle icicle) {
          super.onCreate(icicle);
          setContentView(R.layout.main);
          
          /* Find the MapView we defined in the main.xml. */
          MapView mapViewFromXML = (MapView)this.findViewById(R.id.myMapView);
          /* Retrieve its OverlayController. */
          OverlayController myOC = mapViewFromXML.createOverlayController();
          /* Add a new instance of our fancy Overlay-Class to the MapView. */
          myOC.add(new MyMapDrivingDirectionsOverlay(this), true);
          
          /* Submit one dummy search */
          findViewById(R.id.cmd_submit).setOnClickListener(new OnClickListener(){
               @Override
               public void onClick(View arg0) {
                    MapPoint mpFrom = new MapPoint(37423157, -122085008); // 37.423157,-122.085008
                    MapPoint mpTo = new MapPoint(37495999, -122457508); // 37.495999,-122.457508
                    MyDrivingDirectionsActivity.this.startFetchDirections(mpFrom,"", mpTo, "");
               }
          });
     }

     /** Offers the DrivingDirections to the Overlay. */
     public DrivingDirection getDrivingDirections() {
          return this.myDD;
     }

     private void startFetchDirections(MapPoint from_pos, String from_name,
               MapPoint to_pos, String to_name) {
          /* mDD is a class variable for the activity that will
           * hold an instance of the DrivingDirection object created here. */

          this.myDD = new DrivingDirection(from_pos, from_name, to_pos, to_name);
          if (this.myDD != null) {
               /* Add the request the dispatcher */
               this.getDispatcher().addDataRequest(this.myDD);

               Thread t = new Thread(new Runnable() {
                    public void run() {
                         /* Wait for the search to be complete... */
                         while (!MyDrivingDirectionsActivity.this.myDD.isComplete()) {
                              try {
                                   Thread.sleep(100);
                              } catch (InterruptedException e) {
                                   Log.e("DEBUGTAG", "'!MyDrivingDirectionsActivity.this.myDD.isComplete()' was interruoted.",e);
                              }
                         }
                         /* Check to see if any Placemarks were found..
                          * if 0 then there is no route! */

                         if (MyDrivingDirectionsActivity.this.myDD.getState() != DrivingDirection.SUCCESS_STATUS) {
                              /* Set a flag to let the program know
                               * the directions are done... */

                              MyDrivingDirectionsActivity.this.foundDirections = true;
                         } else{ /* no route.. */
                              MyDrivingDirectionsActivity.this.foundDirections = true;
                              MyDrivingDirectionsActivity.this.myDD = null;
                              /* Let the user know that no route was found... */
                         }
                    }
               });
               t.start();
          }
     }
}


"/src/your_package_structure/MyMapDrivingDirectionsOverlay.java":
Java:
// Created by plusminus on 14:00:27 - 30.01.2008
package org.anddev.android.drivingdirections;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.Log;

import com.google.android.maps.Overlay;
import com.google.android.maps.Point;
import com.google.googlenav.DrivingDirection;
import com.google.googlenav.map.MapPoint;

public class MyMapDrivingDirectionsOverlay extends Overlay {
     
     // ===========================================================
     // Final Fields
     // ===========================================================
     /** The tip of the pin is located at x=5, y=29 */
     private final android.graphics.Point PIN_HOTSPOT = new android.graphics.Point(5,29);
     

     // ===========================================================
     // Fields
     // ===========================================================

     MyDrivingDirectionsActivity itsMapActivity = null;
     Paint pathPaint = null;
     DrivingDirection dd = null;
     
     private Bitmap PIN_START = null;
     private Bitmap PIN_END = null;
     private Bitmap INFO_LOWER_LEFT = null;

     // ===========================================================
     // "Constructors"
     // ===========================================================
     
     public MyMapDrivingDirectionsOverlay(MyDrivingDirectionsActivity map) {
          itsMapActivity = map;
          this.pathPaint = new Paint();
          this.pathPaint.setAntiAlias(true);
          
          PIN_START = BitmapFactory.decodeResource(this.itsMapActivity.getResources(), R.drawable.mappin_blue);
          PIN_END = BitmapFactory.decodeResource(this.itsMapActivity.getResources(), R.drawable.mappin_red);
          INFO_LOWER_LEFT = BitmapFactory.decodeResource(this.itsMapActivity.getResources(), R.drawable.lower_left_info);
     }

     // ===========================================================
     // Getter & Setter
     // ===========================================================

     // ===========================================================
     // Methods
     // ===========================================================

     /** This function does some fancy drawing of the
      * Driving-Directions. It could be shortened a lot. */

     public void draw(Canvas canvas, PixelCalculator pxC, boolean b) {
          super.draw(canvas, pxC, b);
          
          /* Reset our paint. */
          this.pathPaint.setStrokeWidth(4);
          this.pathPaint.setARGB(100, 113, 105, 252);
          // holders of mapped coords...
          int[] screenCoords = new int[2];

          // method in the custom map view to return the DrivingDirection object.
          dd = itsMapActivity.getDrivingDirections();
          if (dd != null) {
               /* First get Start end End Point of the route. */
               MapPoint startPoint = dd.getStartPoint();
               MapPoint endPoint = dd.getEndPoint();

               MapPointToScreenCoords(startPoint, screenCoords, pxC);
               /* Create a path-that will be filled with map-points
                * and will be drawn to the canvas in the end*/

               Path thePath = new Path();
               thePath.moveTo(screenCoords[0], screenCoords[1]);
               
               /* Check to see if the route is too long. */
               if (!dd.routeTooLong()) {
                    /* Retrieve all (Map)Points of the route Found. */
                    MapPoint[] route = dd.getRoute();
                    
                    if(route == null)
                         return;
                    
                    /* Loop through all MapPoints returned. */
                    for (MapPoint current : route) {
                         /* Transform current MapPoint's Lat/Lng
                          * into corresponding point on canvas
                          * using the pixelCalculator. */

                         if(current != null){
                              MapPointToScreenCoords(current, screenCoords, pxC);
                              /* Add point to path. */
                              thePath.lineTo(screenCoords[0], screenCoords[1]);
                         }
                    }
               }
               this.pathPaint.setStyle(Paint.Style.STROKE);
               /* Draw the actual route to the canvas. */
               canvas.drawPath(thePath, this.pathPaint);
               
               /* Finally draw a fancy PIN to mark the start... */
               MapPointToScreenCoords(endPoint, screenCoords, pxC);
               
               canvas.drawBitmap(PIN_END,
                         screenCoords[0] - PIN_HOTSPOT.x,
                         screenCoords[1] - PIN_HOTSPOT.y,
                         pathPaint);
               
               /* ...and the end of the route.*/

               MapPointToScreenCoords(startPoint, screenCoords, pxC);
               canvas.drawBitmap(PIN_START,
                         screenCoords[0] - PIN_HOTSPOT.x,
                         screenCoords[1] - PIN_HOTSPOT.y,
                         pathPaint);
               
               /* Get the height of the underlying MapView.*/
               int mapViewHeight = this.itsMapActivity.findViewById(R.id.myMapView).getHeight();
               
               /* Now some real fancy stuff !*/
               /* Draw a info-menu for the route.*/
               canvas.drawBitmap(this.INFO_LOWER_LEFT, 0, mapViewHeight - this.INFO_LOWER_LEFT.height(), pathPaint);
               
               /* And draw i.e.the distance and time left to the info-menu.*/
               String distance = dd.getFormattedDistance();
               String time = dd.getFormattedTime().replace("Time: ", "");
               pathPaint.setARGB(255,255,255,255);
               this.pathPaint.setStrokeWidth(1);
               this.pathPaint.setStyle(Paint.Style.FILL_AND_STROKE);
               
               pathPaint.setTextSize(24);
               canvas.drawText(time, 5, mapViewHeight - 5, pathPaint);
               pathPaint.setTextSize(16);
               canvas.drawText(distance, 4, mapViewHeight - 35, pathPaint); // 2, 271
               
               /* These methods are to illustrate some
                * of what you can get out of the system. */

//             String startName = dd.getRouteStartLocation();
//             String info = dd.getRouteInfoDescriptor();
               Log.d("DEBUGTAG", dd.getTurns()[0]);
          }
     }
     
     private void MapPointToScreenCoords(MapPoint mp, int[] targetScreenCoords, PixelCalculator pxc){
          Point p = new Point(mp.getLatitude(), mp.getLongitude());
          pxc.getPointXY(p, targetScreenCoords);
     }
}

Thats it Smile


Regards,
plusminus



DrivingDirections_m5.zip
 Description:
For SDK-version m5 :!:
Full Project Source

Download
 Filename:  DrivingDirections_m5.zip
 Filesize:  56.04 KB
 Downloaded:  595 Time(s)


DrivingDirections.zip
 Description:
For SDK-version m3 :!:
Full Project Source

Download
 Filename:  DrivingDirections.zip
 Filesize:  57.46 KB
 Downloaded:  252 Time(s)


_________________

| Android Development Community / Tutorials


Last edited by plusminus on Sun Feb 17, 2008 3:45 pm; edited 7 times in total
Back to top
View user's profile Send private message Send e-mail Visit poster's website
gvkreddyvamsi
Developer


Joined: 21 Jan 2008
Posts: 43
Location: INDIA

PostPosted: Mon Feb 11, 2008 7:13 am    Post subject: Driving directions application is nice Reply with quote

HI,

I have done things well. But i cann't resolve R.java in this application.
can u send zip for this application?

by
vamsi
Back to top
View user's profile Send private message Send e-mail Visit poster's website
plusminus
Site Admin


Joined: 14 Nov 2007
Posts: 1880
Location: Germany

PostPosted: Mon Feb 11, 2008 2:28 pm    Post subject: Reply with quote

Hello,

attached it above Arrow Up

Regards,
plusminus

_________________

| Android Development Community / Tutorials
Back to top
View user's profile Send private message Send e-mail Visit poster's website
tv_sathish
Developer


Joined: 09 Jan 2008
Posts: 29

PostPosted: Tue Feb 12, 2008 12:20 pm    Post subject: Questions regarding this tutorial... Reply with quote

Hi Plusminus,
I am getting a compilation error in the line:

Code:

      /* Submit one dummy search */
      findViewById(R.id.cmd_submit).setOnClickListener(new OnClickListener(){
         @Override
         [b]public void onClick(View arg0) {[/b]


Error is The method onClick(View) of type new View.OnClickListener(){} must override a superclass method MyDrivingDirectionsActivity/src/org/anddev/android/drivingdirections/MyDrivingDirectionsActivity.java line 34

What could be the problem?

By the by, if we are using the inbuilt DrivingDirections, why haven't u used DrivingDirectionsRenderer to draw the Driving Directions? Is that class not fully implemented?

And I had posted a few questions in the "Coding Problems" forum, waiting for your reply, let me know if I am disturbing you during exam time...

Hey, I was having a similar idea that you are planning as AndNav, but since you are already into it and 75% through, let me not compete and spoil your chances too...all the best man for that entry...

Regards,
Sathish
Back to top
View user's profile Send private message
plusminus
Site Admin


Joined: 14 Nov 2007
Posts: 1880
Location: Germany

PostPosted: Tue Feb 12, 2008 1:40 pm    Post subject: Reply with quote

Hello tv_sathish,

you've got two possibilities:
a.) simply Comment the @Override-Annotation out
b.) Update to JDK 1.6 (preferred)

This happens because the JDK 1.5, you are probably running, does accept @Override-Annotation only for overridden methods of Super-Classes and not for overriding functions of Interfaces we are implementing here.

On JDK 1.6 this Annotation is possible. Rolling Eyes

Hm googling for DrivingDirectionRenderer returns unbelievable two results Exclamation In the SDK-Documentation, even DrivingDirection returns zero results Rolling Eyes.
Both are probably not 100% ready to be used Rolling Eyes

Feel free to submit any similar application, because competition is the best for the market Smile

Regards,
plusminus

_________________

| Android Development Community / Tutorials
Back to top
View user's profile Send private message Send e-mail Visit poster's website
wrapware
Freshman


Joined: 17 Jan 2008
Posts: 7
Location: Germany

PostPosted: Wed Feb 13, 2008 2:54 pm    Post subject: Reply with quote

Hi plusminus,

its a really nice app. There is one question:

Why are you assign two time the 'true' to foundDirections?

Java:
                       if (MyDrivingDirectionsActivity.this.myDD.numPlacemarks() > 0) {
                              /* Set a flag to let the program know
                               * the directions are done... */

                              MyDrivingDirectionsActivity.this.foundDirections = true;
                         } else{ /* no route.. */
                              MyDrivingDirectionsActivity.this.foundDirections = true;
                              MyDrivingDirectionsActivity.this.myDD = null;
                              /* Let the user know that no route was found... */
                         }


In your code is no reference to the variable foundDirections, so whats the reason for the whole thread processing with the foundDirections member? I know the user should not wait in the main thread, but ...
Maybe there is a dig deeper I'm unable to see?

Regards,
ww
Back to top
View user's profile Send private message
plusminus
Site Admin


Joined: 14 Nov 2007
Posts: 1880
Location: Germany

PostPosted: Wed Feb 13, 2008 3:01 pm    Post subject: Reply with quote

Hello wrapware,

the second one should be false of course Embarassed (corrected it above)

I didn't remove it because it could become useful if one takes this code as a base for another Application.

Regards,
plusminus

_________________

| Android Development Community / Tutorials
Back to top
View user's profile Send private message Send e-mail Visit poster's website
wrapware
Freshman


Joined: 17 Jan 2008
Posts: 7
Location: Germany

PostPosted: Fri Feb 15, 2008 9:30 am    Post subject: Reply with quote

Hi plusminus

Yesterday I switch to m5_rc14 and after some modifications, your code run still fine, but with one difference.

The onDraw method is now called immediately after returning, when nothing is available for drawing (route==null)

Java:
   /** This function does some fancy drawing of the
      * Driving-Directions. It could be shortened a lot. */

     public void draw(Canvas canvas, PixelCalculator pxC, boolean b) {
          super.draw(canvas, pxC, b);

          // ...

                 if(route == null)
                         return;

So the cpu usage run up to 100%, but there is nothing to draw. Is there a way to tell the
Java:
MyMapDrivingDirectionsOverlay
or the
Java:
maps.Overlay
to giveup redrawing? I dump the callers hierarchie when if(route == null) happens, and thats the stack for every cycle:

Java:
     at MyMapDrivingDirectionsOverlay.draw(MyMapDrivingDirectionsOverlayjava:88 )
      at com.google.android.maps.Overlay.draw(Overlay.java:154)
      at com.google.android.maps.OverlayBundle.draw(OverlayBundle.java:43)
      at com.google.android.maps.MapView.onDraw(MapView.java:337)
      at android.view.View.draw(View.java:4574)
      at android.view.ViewGroup.drawChild(ViewGroup.java:1089)
      at android.view.ViewGroup.dispatchDraw(ViewGroup.java:915)
      at android.view.View.draw(View.java:4550)
      at android.widget.FrameLayout.draw(FrameLayout.java:208)
      at android.view.ViewGroup.drawChild(ViewGroup.java:1089)
      at android.view.ViewGroup.dispatchDraw(ViewGroup.java:915)
      at android.view.View.draw(View.java:4578)
      at android.view.ViewGroup.drawChild(ViewGroup.java:1089)
      at android.view.ViewGroup.dispatchDraw(ViewGroup.java:915)
      at android.view.View.draw(View.java:4550)
      at android.widget.FrameLayout.draw(FrameLayout.java:208)
      at android.view.ViewGroup.drawChild(ViewGroup.java:1089)
      at android.view.ViewGroup.dispatchDraw(ViewGroup.java:915)
      at android.view.View.draw(View.java:4550)
      at android.view.ViewGroup.drawChild(ViewGroup.java:1089)
      at android.view.ViewGroup.dispatchDraw(ViewGroup.java:915)
      at android.view.View.draw(View.java:4550)
      at android.widget.FrameLayout.draw(FrameLayout.java:208)
      at android.view.ViewGroup.drawChild(ViewGroup.java:1089)
      at android.view.ViewGroup.dispatchDraw(ViewGroup.java:915)
      at android.view.View.draw(View.java:4578)
      at android.widget.FrameLayout.draw(FrameLayout.java:208)
      at android.view.ViewRoot.draw(ViewRoot.java:531)
      at android.view.ViewRoot.performTraversals(ViewRoot.java:429)
      at android.view.ViewRoot.handleMessage(ViewRoot.java:584)
      at android.os.Handler.dispatchMessage(Handler.java:80)
      at android.os.Looper.loop(Looper.java:91)
      at android.app.ActivityThread.main(ActivityThread.java:3052)
      at java.lang.reflect.Method.invokeNative(Native Method)
      at java.lang.reflect.Method.invoke(Method.java:356)
      at android.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1547)
      at android.os.ZygoteInit.main(ZygoteInit.java:1445)
      at android.dalvik.NativeStart.main(Native Method)



Regards,
ww
Back to top
View user's profile Send private message