Monday, March 7, 2011

Get Direction on Google maps using Android


Goal:
The goal of this assignment is to create an application for android. User have list of addresses saved in his mobile database. If user clicks on any of the address, application should open Google map application which shows the path from user’s current location to selected location in the list.

Tools:

·         Android API’s (SDK)
·         Google Map API’s (ADT)
·         Eclipse
·         SQLite

Installation:

First we need to install eclipse which we will use for server side coding. It is recommended to use eclipse with Android SDK. Download Eclipse Classic from here. After installing eclipse, we need to download and install Android SDK starter package and then we need to download and update our eclipse with ADT Plugin. Complete details of installation can be found on Android Developer website. After installing SDK and ADT Plugin, open eclipse and go to Window->Android SDK and AVD Manager. Click ‘New’ and create new Android Virtual Device (AVD).




Creating New Project:

Now we will create a new project by select File->New->Project. In the New Project wizard select Android->Android Project. Now create new project by specifying Project name “Directory”, select latest Google API’s, with Application name “Directory”,  package name “com.example.directory” and Create Activity “Directory”, then click next and finish. Now we can see that in our project we have different directories of src, gen, Google APIs, assets and res. Some files like AndroidManifest.xml, default.properties and proguard.cfg.
Open AndroidManifest.xml file and add this line in between <Application> tag
<uses-library android:name="com.google.android.maps" />
Add this line in between <manifest> tag
<uses-permission android:name="android.permission.INTERNET" />

Creating and connecting to a database:

SQLite is an open-source server-less database engine. SQLite supports transacations and has no configuration required. SQLite adds powerful data storage to mobile and embedded apps without a large footprint.

Accessing SQLite Database:

To access SQLite database we will create new java class. Name it “DataBaseHelper”. Code of this file will be like this

DataBaseHelper.java

public class DataBaseHelper extends SQLiteOpenHelper{
                //The Android's default system path of your application database.
    private static String DB_PATH = "/data/data/com.example.directory/databases/";
     private static String DB_NAME = "myDirectory";
     private SQLiteDatabase myDataBase;
     private final Context myContext;
     /**
     * Constructor
     * Takes and keeps a reference of the passed context in order to access to the application assets and resources.
     * @param context
     */
    public DataBaseHelper(Context context) {
                super(context, DB_NAME, null, 1);
        this.myContext = context;
    }         
   /**
     * Creates a empty database on the system and rewrites it with your own database.
     * */
    public void createDataBase() throws IOException{
                boolean dbExist = checkDataBase();
                if(dbExist){
                                //do nothing - database already exist
                }else{
                                //By calling this method and empty database will be created into the default system path
               //of your application so we are gonna be able to overwrite that database with our database.
                this.getReadableDatabase();
                try {
                                                copyDataBase();
                                } catch (IOException e) {
                                throw new Error("Error copying database");
                }
                }
    }
    /**
     * Check if the database already exist to avoid re-copying the file each time you open the application.
     * @return true if it exists, false if it doesn't
     */
    public boolean checkDataBase(){
                SQLiteDatabase checkDB = null;
                try{
                                String myPath = DB_PATH + DB_NAME;
                                checkDB = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
                }catch(SQLiteException e){
                                //database does't exist yet.
                }
                if(checkDB != null){
                                checkDB.close();
                }
                return checkDB != null ? true : false;
    }
     /**
     * Copies your database from your local assets-folder to the just created empty database in the
     * system folder, from where it can be accessed and handled.
     * This is done by transfering bytestream.
     * */
    private void copyDataBase() throws IOException{
                //Open your local db as the input stream
                InputStream myInput = myContext.getAssets().open(DB_NAME);
                // Path to the just created empty db
                String outFileName = DB_PATH + DB_NAME;
                //Open the empty db as the output stream
                OutputStream myOutput = new FileOutputStream(outFileName);
                //transfer bytes from the inputfile to the outputfile
                byte[] buffer = new byte[1024];
                int length;
                while ((length = myInput.read(buffer))>0){
                                myOutput.write(buffer, 0, length);
                }
                //Close the streams
                myOutput.flush();
                myOutput.close();
                myInput.close();
     }
     public void openDataBase() throws SQLException{
                //Open the database
        String myPath = DB_PATH + DB_NAME;
                myDataBase = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
     }
     @Override
                public synchronized void close() {
                    if(myDataBase != null)
                                    myDataBase.close();
                    super.close();
                }
                @Override
                public void onCreate(SQLiteDatabase db) {
                }
                @Override
                public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                }
         // Add your public helper methods to access and get content from the database.
       // You could return cursors by doing "return myDataBase.query(....)" so it'd be easy
       // to you to create adapters for your views.
 }

Now you can create a new instance of this DataBaseHelper class and call the createDataBase() and openDataBase() methods like this in your activity class

DataBaseHelper myDbHelper = new DataBaseHelper();
        myDbHelper = new DataBaseHelper(this);
 
        try {
 
               myDbHelper.createDataBase();
 
        } catch (IOException ioe) {
 
               throw new Error("Unable to create database");
 
        }
 
        try {
 
               myDbHelper.openDataBase();
 
        }catch(SQLException sqle){
 
               throw sqle;
 
        }
 

Adding New Table:

Android stores SQLite databases in /data/data/[application package name]/databases.

sqlite3 from adb shell

In command prompt cd to C:\Program Files\Android\android-sdk-windows\platform-tools.
C:\Program Files\Android\android-sdk-windows\platform-tools >adb devices
List of devices attached
emulator-5554   device
C:\Program Files\Android\android-sdk-windows\platform-tools >adb  -e shell
#cd /data/data/com.example.directory/databases
#sqlite3 myDirectory
SQLite version 3.6.22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> Create Table Directory("lat" Text, "lng" Text, "address" Text);
sqlite> insert into Directory values ('38.733632','-9.139552','Arroios, Lisboa, Portugal');

you can add more values if you want.

Populate the list and GetDirection:

Now we will populate the list as shown


Directory.java

Open the Directory.java file and extends it with ListActivity.
The whole java class will look like this 
public class Directory extends ListActivity  {
     
      private String[] results;
      private String[] lati;
      private String[] lngi;
      private String lat;
      private String lng;
      MapView mapView;
      MapController mc;
     
          /** Called when the activity is first created. */
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       
        DataBaseHelper myDbHelper = new DataBaseHelper(this);
       
    try {

      myDbHelper.createDataBase();

      } catch (IOException ioe) {
            throw new Error("Unable to create database");
      }
     
      try {

            myDbHelper.openDataBase();         
            Cursor c = myDbHelper.getReadableDatabase().rawQuery("select lat,lng,address from Directory;", null);
            results = new String[c.getCount()];
            lati = new String[c.getCount()];
            lngi = new String[c.getCount()];
           
            if (c != null ) {
                  int x=0; 
            if (c.moveToFirst()) { 
                do 
                  lati[x] = c.getString(0);
                  lngi[x] = c.getString(1);
                    results[x] = c.getString(2);
                         
                       //results.add(b1); 
    
                       x=x+1; 
                } while (c.moveToNext()); 
             } 
                  myDbHelper.close();

      }
      }catch(SQLException sqle){

            throw sqle;

      }
       
        setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, results));
       
        ListView lv = getListView();
        lv.setTextFilterEnabled(true);

        lv.setOnItemClickListener(new OnItemClickListener() {
          public void onItemClick(AdapterView<?> parent, View view,
              int position, long id) {
            TextView tv=(TextView)view;
            tv.getText();
            String place = tv.getText().toString();
            getlatlngbyplace(place);
        
             lat = lat.replace(".", "");
             lng = lng.replace(".", "");
             
               int intlat=Integer.parseInt(lat);
               int intlng=Integer.parseInt(lng);
           
              GeoPoint p=new GeoPoint(intlat, intlng);
             
              GeoPoint point = new GeoPoint(38707582,-9134853);
              
              Intent intentt = new Intent(android.content.Intent.ACTION_VIEW,  
                        Uri.parse(getUrl(point, p)));     
                        startActivity(intentt);
                  Toast.makeText(getApplicationContext(), "Hello",
                      Toast.LENGTH_SHORT).show();
          }
        });
  }  
     
      private void getlatlngbyplace(String place)
      {
              DataBaseHelper myDbHelper = new DataBaseHelper(this);
                  myDbHelper.openDataBase();
                 
                  Cursor c = myDbHelper.getReadableDatabase().rawQuery("select lat,lng,address from Directory where address='"+place+"';", null);
                  if (c != null ) {
                        if (c.moveToFirst())
                        {                                
                               lat=c.getString(0).toString();
                               lng=c.getString(1).toString();
                        }
                  }
                  myDbHelper.close();          
           
      }
     
    public String getUrl(GeoPoint src, GeoPoint dest){ 
       
        StringBuilder urlString = new StringBuilder(); 
         
        urlString.append("http://maps.google.com/maps?f=d&hl=en"); 
        urlString.append("&saddr="); 
        urlString.append(Double.toString((double) src.getLatitudeE6() / 1.0E6)); 
        urlString.append(","); 
        urlString.append(Double.toString((double) src.getLongitudeE6() / 1.0E6));  
        urlString.append("&daddr=");// to 
        urlString.append(Double.toString((double) dest.getLatitudeE6() / 1.0E6));  
        urlString.append(","); 
        urlString.append(Double.toString((double) dest.getLongitudeE6() / 1.0E6));  
        urlString.append("&ie=UTF8&0&om=0&output=kml"); 
          
        return urlString.toString(); 
        } 
}Now when you run the application and click anyone of the address of the list, it will show google map like this


Saving selected location in the databases:

So far what we have done, we retrieve data from the database and show direction on the google map. Let extend our application by saving locations in the database, so that user can see it on the list.
Lets create one new class for FronScreen. Where we will put to button, one will navigate to list of address and other one will open google map where user can place the marker and save that location to the database.
Every time when we have to create new activity, it should be define in AndroidMenifest.xml. As now FrontScreen class will be our first displaying screen, so we will replace .directory activity with .FronScreen activity and add new one with .directory. The whole code will look like this
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.example.directory"
      android:versionCode="1"
      android:versionName="1.0">


    <application android:icon="@drawable/icon" android:label="@string/app_name">

      <uses-library android:name="com.google.android.maps" />

        <activity android:name=".Directory" />
        <activity android:name=".FrontScreen"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>   

      <uses-permission android:name="android.permission.INTERNET" />

</manifest>

Front.xml

For every class display we have layout file which show view of the screen. So go to res->layout and add Front.xml. It will display two buttons on the screen “Directory” and “Save Address”. So Front.xml code will look like this
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">
   <Button android:id="@+id/Directory"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Directory"/>
   <Button android:id="@+id/SaveAddress"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Save Location"/>
</LinearLayout>

FrontScreen.java

Now FrontScreen.java code will be like this
public class FrontScreen extends Activity {
     
       private Button Directory;
       private Button Save;
     
    /** Called when the activity is first created. */
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.front);
       
        Directory = (Button) findViewById(R.id.Directory);
        Save = (Button) findViewById(R.id.SaveAddress);
       
        Directory.setOnClickListener(new View.OnClickListener() {
          public void onClick(View v) {
              Intent intent = new Intent(FrontScreen.this, Directory.class);
              startActivity(intent);
          }
      });
       
        Save.setOnClickListener(new View.OnClickListener() {
          public void onClick(View v) {
              Intent intentt = new Intent(FrontScreen.this, MapsActivity.class);
              startActivity(intentt);
          }
      });
       
}
}

If you see in front.java class we displayed two buttons “Directory” and “Save”. “Directory” button redirect to our previous activity class (Directory.java). “Save” button is redirecting to MapsActivity.java class. So create new class, name it MapsActivity.java. In this class we will display map, where we can put the marker and save it to our database.

Map.xml

For MapsActivity class, we have to define its view first. Create map.xml file in layout directory and its code will be like this
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

     <com.google.android.maps.MapView
     xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/mapView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:enabled="true"
        android:clickable="true"
        android:apiKey="0i8uGM9k9JAlCRkTbC9Rd8lm-VQjcut9xXWlgMA"
        />
       
     <LinearLayout android:id="@+id/zoom"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"             
        >

     </LinearLayout>
     <Button android:id="@+id/save"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10px"
        android:layout_alignParentBottom="true"
        android:text="Save"/>       

</RelativeLayout>
Here we define our map, its zoom buttons and one save (to save marker) button.

MapActivity.java

MapActivity code will be like this
public class MapsActivity extends MapActivity
{   
      MapView mapView;
        MapController mc;
          GeoPoint p;
          String latit;
          String lngit;
          private Button save;
          static final int TIME_DIALOG_ID = 0;
          DataBaseHelper myDbHelper = new DataBaseHelper(this);
         
          class MapOverlay extends com.google.android.maps.Overlay
          {
              @Override
              public boolean draw(Canvas canvas, MapView mapView,
              boolean shadow, long when)
              {
                  super.draw(canvas, mapView, shadow);                  
       
                  //---translate the GeoPoint to screen pixels---
                  Point screenPts = new Point();
                  mapView.getProjection().toPixels(p, screenPts);
       
                  //---add the marker---
                  Bitmap bmp = BitmapFactory.decodeResource(
                      getResources(), R.drawable.mapmarker);           
                  canvas.drawBitmap(bmp, screenPts.x, screenPts.y-50, null);         
                  return true;
              }
             
                @Override
                public boolean onTouchEvent(MotionEvent event, MapView mapView)
              {  
                  if (event.getAction() == 1) {
                        p = mapView.getProjection().fromPixels((int) event.getX(), (int) event.getY());
           
                        mc.animateTo(p);
                        return true;
                        } else return false;
              }   
          }
         
   
         
             /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {      
        super.onCreate(savedInstanceState);
        setContentView(R.layout.map);
              
        MapView mapView = (MapView) findViewById(R.id.mapView);
        LinearLayout zoomLayout = (LinearLayout)findViewById(R.id.zoom); 
        View zoomView = mapView.getZoomControls();
       
        zoomLayout.addView(zoomView,
                new LinearLayout.LayoutParams(
                    LayoutParams.WRAP_CONTENT,
                    LayoutParams.WRAP_CONTENT));
            mapView.displayZoomControls(true);
           
            mc = mapView.getController();
            String coordinates[] = {"38.707582", "-9.134853"};
            double lat = Double.parseDouble(coordinates[0]);
            double lng = Double.parseDouble(coordinates[1]);
    
            p = new GeoPoint(
                (int) (lat * 1E6),
                (int) (lng * 1E6));
    
            mc.animateTo(p);
            mc.setZoom(17);
           
            //---Add a location marker---
            MapOverlay mapOverlay = new MapOverlay();
            List<Overlay> listOfOverlays = mapView.getOverlays();
            listOfOverlays.clear();
            listOfOverlays.add(mapOverlay);     
            
            //---Add Save Button---
            save = (Button) findViewById(R.id.save);
            // add a click listener to the button
            save.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                  myDbHelper.openDataBase();
                  AlertDialog.Builder builder = new AlertDialog.Builder(MapsActivity.this);
                     final EditText et=new EditText(MapsActivity.this);
                     builder.setMessage("Enter the name of the place")
                              .setView(et)
                         .setCancelable(false)
                         .setPositiveButton("Save", new DialogInterface.OnClickListener() {
                             public void onClick(DialogInterface dialog, int id) {
                                    myDbHelper.getReadableDatabase().execSQL("insert into Directory values('"+p.getLatitudeE6()+"','"+p.getLongitudeE6()+"','"+ et.getText() +"');");
                              Toast.makeText(getApplicationContext(), "Address saved successfully",
                                  Toast.LENGTH_SHORT).show();
                                  MapsActivity.this.finish();
                             }
                         })
                         .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                             public void onClick(DialogInterface dialog, int id) {
                                  dialog.cancel();
                             }
                         });
                  AlertDialog alert = builder.create();
                  alert.show();               
                }
            });
           
           
            mapView.invalidate();       
    }
   

   
    @Override
    protected boolean isRouteDisplayed() {
        return false;
    }
}

Output

Now when you will run the application, the output will be like this

FrontScreen

Now when clicked “Save Location” button, it will redirect to map view.

DirectoryScreen

On this map you can place marker on any location you want to save and then click “Save” button on lower left corner. It will open a dialog box show

Show Dialog
Here you can save the location with your specified name and this location will be saved in your Directory list. On selecting that address you can see its path.


Here I put one point static so you can put the code to get your present location by using GPS. With this application you can save address on your mobile local database and get direction to that places any time when you required.