Click here to Skip to main content
15,867,750 members
Articles / Mobile Apps
Article

Walkers Mapped Gps

Rate me:
Please Sign up or sign in to vote.
4.92/5 (5 votes)
10 Jun 2012CPOL6 min read 29.2K   1.8K   23   1
Complete description of an Android gps application with links to Android code.

Introduction

This article is a complete description of an Android gps application.
Links to all the Android code used in this application are provided.

The WalkersMap.zip has the complete project for this application.
I use eclipse Indigo with the Android SDK package. Open the PROJECT FILE
and eclipse should load this application’s code.

Image 1

Android has a complete description on how to load the SDK, however I found two problems.

There is a problem when using windows 7. We cant load the android package
when eclipse is stored in C:\program. It will load if you run eclipse as administrator.
I  stored eclipse in the video folder and then  I don’t need right click.
Android virtual devices are communicated with via ports. If you have a zealous firewall
it can block its operation. I needed to add an allowed application to the firewall.

Program operation:

At its basic level the program maps gps locations. We have the following specifications:

1. The map is a 1km grid, it contains no altitude information.
    It uses Universal Transverse Mercator geographic coordinates.
2. The user can have a UTM terrain map. Way points from this map can
     be entered into the program and displayed along with the gps locations.
3. The map will have a maximum 200km by 200km area.
    The display can be zoomed in/out moved left/right, up/down.
    The largest display is a 50km by 50 km area. The smallest is 1km by 1km.
4. Gps is a heavy user of battery power so the phone must sleep between gps fixes.
    Fixes can be set to automatic. Wake the phone once every 5 minutes for the fix.
5. All the gps locations need to be stored in the phones flash memory.
    They are then restored to the application after a phone reboot.

Splash screen:

Image 2

To do this I use a CountDownTimer.

In onCreate the main.xml has the splash screen image.
We hold this image for 3 seconds then reload the screen with the graphics
map page constructed in mView.

setContentView(R.layout.main);
//
new CountDownTimer(3000,1000)
   {
        public void onFinish()
        {
         setContentView(mView);
        }//end of on finish
        public void onTick(long arg0){} 
       }.start();

Map Screen:

Image 3

The screen is constructed in the MapView class. We pass the screen dimensions to the class
and return the screen with all the data points plotted.

Java
DisplayMetrics metrics = new DisplayMetrics();  ////
getWindowManager().getDefaultDisplay().getMetrics(metrics);
screenW =metrics.widthPixels;
screenH = metrics.heightPixels;
mView = new MapView(this,screenW,screenH);

The onDraw method is called every time the screen is redrawn. This happens on startup
and any time I call   mView.invalidate();  //redraw screen

Java
public class MapView extends View
{
//
public MapView(Context context, float sW, float sH) 
  {
         super(context);
         setFocusable(true);
         d = sW/260;  //set the screen delta
         h = sH;  //set screen height      
         constructSigns();  //construct the button signs        
  }//end of constructor
  
 @Override protected void onDraw(Canvas canvas) 
 {
       // all graphics done here
       drawButtons(canvas);
       }//end of onDraw
//
}//end of MapView class

The program uses scaling and shifting to generate the map. We can zoom in/out,
shift left/right, shift up/down. This allows the user to center his locations on the
map at the optimum viewing size.
The drawButtons method makes use of Android Path and matrix API’s.

Integral to constructing the map is the conversion of the lat/long
gps fixes(degrees) into meters.
I use the Universal Transverse Mercator geographic coordinate system to do the conversion.

Universal Transverse Mercator:

The system divides the Earth into sixty zones, each a six-degree band of longitude.
The main reason I use UTM is because most bush walkers will have a UTM map of
the area they traverse. They will plot their walk on the paper map and then select the way points in this application.
They then set continuous mode and the program will plot the gps fixes over the selected way point path.
The conversion mathematics comes from  the Defense Mapping Agency Technical Manual.

The conversion is done in the UTS class.

Java
public class UTS 
{
 static long EastingI = 0//easting in meters
 static long NorthingI = 0; //northing in meters
 static int ZoneNumber = 0//the easting grid zone
 static char ZoneDesignator = 'C';
 
 private static double CM  = 0;//153;//99;  //central meridian
 private final static char ZD[] = {'C','D','E','F','G','H','J','K','L','M',
                  'N','P','Q','R','S','T','U','V','W','X'}; //the zone designators
  
  
 public static void ConvertGeoUts(double lat, double lon)
 {
   ZoneNumber = ((int) Math.floor(lon) + 180)/6 + 1;
   CM = (ZoneNumber - 1)*6 + 3 - 180;
   byte i = 0;
   for(i = 0; i < 20; i++)
   {
     if(lat + 80 < i*8) { break; }  //break when find zone
   }//end of get zone designator
   ZoneDesignator = ZD[i - 1];  // set value
   N_GEOtoUTS(lat, lon);
   E_GEOtoUTS(lat, lon);     
 }//end of geo to uts conversion
//
}//end of UTS class

The methods are all static so that they are held permanently  in RAM.
This gives faster operation as I do not need to create an instance each time it’s needed.
UTS.ConvertGeoUts(lat , lon);  //this is all thats needed

Map Screen Menu:

Image 4

The menu is defined in the menu.xml file:

XML
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="<a href="http://schemas.android.com/apk/res/android">http://schemas.android.com/apk/res/android</a>" >
 <item android:id="@+id/menu_help"
      android:icon="@android:drawable/ic_menu_help" 
      android:title="@string/menu_help" />
  
  <item android:id="@+id/menu_reference" 
      android:icon="@android:drawable/ic_menu_compass" 
      android:title="@string/menu_gpsloc" />
  
  <item android:id="@+id/menu_locations" 
      android:icon="@android:drawable/ic_menu_myplaces" 
      android:title="View Locations" />
  
  <item android:id="@+id/menu_start" 
      android:icon="@android:drawable/ic_menu_more" 
      android:title="Start Continuous" />
  
  <item android:id="@+id/menu_delete" 
      android:icon="@android:drawable/ic_menu_delete" 
      android:title="Delete this Walk" />
  
  <item android:id="@+id/menu_cancel" 
      android:icon="@android:drawable/ic_menu_close_clear_cancel" 
      android:title="Cancel Continuous" />
  
  
</menu>

The menu is implemented by:

Java
@Override
    public boolean onCreateOptionsMenu(Menu menu) 
    {
        super.onCreateOptionsMenu(menu);
        MenuInflater mi = getMenuInflater();
        mi.inflate(R.menu.menu, menu); 
        return true;
    }//end of inflate menu specified in XML menu file
    
    @Override
    public boolean onMenuItemSelected(int featureId, MenuItem item) 
    {
        switch(item.getItemId())
        {
        case R.id.menu_help: 
         Intent i =new Intent(this,HelpFile.class);
         startActivity(i);  //show the help file
            return true; 
        case R.id.menu_reference: 
         Intent igps =new Intent(this,selectReference.class);
        startActivity(igps);  //show the GPS file 
           return true;
        case R.id.menu_locations: 
          Intent igl =new Intent(this,ViewLocations.class);
         startActivity(igl);  //show the GPS file 
            return true;
        case R.id.menu_delete: 
           DeleteWalk();  //bring up the dialog
             return true;
        case R.id.menu_start:         
         long alarm = 300000; //5 minute
         if(!continuous){
         CG.setContinuous((long) 0, alarm);
         continuous = true; //we are running
         mView.invalidate();  //redraw screen
         }//end of start continuous
            return true;
        case R.id.menu_cancel:
         if(continuous){
           CG.cancelContinuous();  //bring up the dialog
           continuous = false;
           mView.invalidate();  //redraw screen
         }//end of cancel continuous
              return true;
        }//end of case      
        return super.onMenuItemSelected(featureId, item);
    }//end of menu item selected

Map Screen Delete:

Image 5

If the users wishes to delete the entire walk I ask with a dialog if this is their wish:

private void DeleteWalk()
    {
     mDbHelper = new LocationsDatabase(this);
        mDbHelper.open();
     AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage("Do you want to delete all gps and way point locations ")
            .setTitle("'Delete this Walk")
               .setCancelable(false)
               .setPositiveButton("Yes Delete All", new DialogInterface.OnClickListener() {
                   public void onClick(DialogInterface dialog, int id) {
                    mDbHelper.DeleteAllLocation(); //delete all rows in sqlite                   
                    NumLoc = 0; // reset the location number
                       Location[0][0]=0; Location[0][1]=0; Location[0][2]=0; 
                       Location[0][3]=0; Location[0][4]=0//reset first gps
                    NumWay = 0//reset the waypoint number
                       WayPoint[0][0]=0; WayPoint[0][1]=0; 
                    mDbHelper.close();  //Don't need now
                    mView.invalidate();  //redraw screen
                    }
               })       
               .setNegativeButton("No Cancel", new DialogInterface.OnClickListener() {
                          public void onClick(DialogInterface dialog, int id) {
                            mDbHelper.close();  //Don't need now
                               dialog.cancel();           }
                   
               });
        final AlertDialog alert = builder.create();
        alert.show();  
    }//end of delete walk

Data Restoration:

Applications are stored in flash memory(sometimes called ROM).
This is non volatile and is not lost when the phone is powered down.
When the application is started, the program is loaded into the RAM to allow direct access by the CPU.
All data that is generated by the program is also stored in RAM.
If the program loses focus because of a phone call or user operation, the data remains in RAM.
If the user or the system puts the phone to sleep, the data remains in RAM.
However if the phone is turned off the RAM loses the program and any data generated.
The program remains in flash memory and is reloaded by the android operating system
when the user selects the program.
Any data you need to restore must be first stored in the flash.
Android provides methods for saving and restoring data in the flash.

I use two types of data storage.
Shared Preferences for individual values, start latitude/longitude and  Number of gps/waypoints.
The data is stored in onStop whenever the map page loses focus.

 protected void onStop()
      {       
     super.onStop();
     // We need an Editor object to make preference changes.      
     //Store private primitive data in key-value pairs.
     SharedPreferences settings = getSharedPreferences("gps", 0);  //the folder      
     SharedPreferences.Editor editor = settings.edit();      
     editor.putInt("GpsLocations", NumLoc);
     editor.putInt("WayPoints", NumWay);
     editor.putFloat("LatRef", (float) MapView.latRef);
     editor.putFloat("LonRef", (float) MapView.lonRef);  //save these key values
     // Commit the edits!      
     editor.commit();   
     }//end of on stop

The data is restored whenever the program is started in onCreate.

Java
SharedPreferences settings = getSharedPreferences("gps",0);     
        MapView.latRef = settings.getFloat("LatRef", 0);
        MapView.lonRef = settings.getFloat("LonRef", 0);
        NumLoc = settings.getInt("GpsLocations", 0); //restore gps locations number      
        NumWay = settings.getInt("WayPoints", 0); //restore way point number

To store the data from 576 gps locations and  50 way points we need a database.

SQLite database:

The database is enabled in the public class LocationsDatabase
In this class we have a nested class that uses the recommended method
to create a new SQLite database.

Java
private static class DatabaseHelper extends SQLiteOpenHelper
 {
     DatabaseHelper(Context context)
     {
         super(context, DATABASE_NAME, null, DATABASE_VERSION);
     }//end of constructor  database is gps
     @Override
     public void onCreate(SQLiteDatabase db) { db.execSQL(DATABASE_CREATE);}  
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion,int newVersion) 
     {
         db.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLE);
         onCreate(db);
     }//end of upgrade database
 }//end of nested class DatabaseHelper

The LocationsDatabase class has these methods which are used to store and access
data from the SQLite database(which is in the flash memory).

Java
public LocationsDatabase open() throws SQLException 
//
public void close() { mDbHelper.close();}  //caller closes data base
//
public long AddLocation(int row, String type, double latoreast, double lonornorth,
                        double altitude, double time, double accuracy) 
//
public Cursor ReadLocation(long rowId) throws SQLException
//
public boolean DeleteAllLocation() 

To access the database we need to create an instance of LocationsDatabase, open
the data base and then store the data. Finally we must close the database.
So to store a way point we have:

Java
mDbHelper = new LocationsDatabase(this);
mDbHelper.open();
//
mDbHelper.AddLocation(WalkersMapGps.NumWay + WalkersMapGps.NumLoc,"way",
   WalkersMapGps.WayPoint[WalkersMapGps.NumWay][0],
   WalkersMapGps.WayPoint[WalkersMapGps.NumWay][1],0,0,0);  
         WalkersMapGps.NumWay += 1;
//
mDbHelper.close();  //Don't need now

There are two types of data stored in the database. Gps locations labeled “gps” and
way points labeled “way” .To restore all the data we read the data base:

Java
Cursor ReadRow =mDbHelper.ReadLocation(i);  //rows 0 1 2 3 ect
startManagingCursor(ReadRow);
String type = ReadRow.getString(ReadRow.getColumnIndexOrThrow
              (LocationsDatabase.KEY_TYPE));
if(type.equals("gps"))
{
Location[l][0] = ReadRow.getDouble(ReadRow.
                 getColumnIndexOrThrow(LocationsDatabase.KEY_LATOREAST));
Location[l][1] = ReadRow.getDouble(ReadRow.
                 getColumnIndexOrThrow(LocationsDatabase.KEY_LONGORNORTH));
Location[l][2] = ReadRow.getDouble(ReadRow.
                 getColumnIndexOrThrow(LocationsDatabase.KEY_ALTITUDE));
Location[l][3] = ReadRow.getDouble(ReadRow.
                 getColumnIndexOrThrow(LocationsDatabase.KEY_TIME));
Location[l][4] = ReadRow.getDouble(ReadRow.
                 getColumnIndexOrThrow(LocationsDatabase.KEY_ACCURACY));
l += 1//count this one
}//end of its a gps value
else  //we have a way point
{
WayPoint[w][0] =(int) ReadRow.getDouble(ReadRow.
                getColumnIndexOrThrow(LocationsDatabase.KEY_LATOREAST));
WayPoint[w][1] =(int) ReadRow.getDouble(ReadRow.
                getColumnIndexOrThrow(LocationsDatabase.KEY_LONGORNORTH));
w += 1; //count this one
}//end of its a way point

Gps Locations:

Image 6

The GpsActivity class has all the code necessary to get the gps fixes.
If a fix cannot be obtained within 3 minutes the activity will close.
I use a CountDownTime to close the activity.
The top button(gps on/off) can be selected to redirect the user to the Android
location settings page:

Java
gpsOn = (Button) findViewById(R.id.location);
 gpsOn.setOnClickListener(new View.OnClickListener() 
            {      
      public void onClick(View v) {
       Intent i = new Intent(Settings.
                                     ACTION_LOCATION_SOURCE_SETTINGS);
                startActivity(i);  //show the gps settings
                finish();  //exit this program
       }//end have click
      });////end of click listener

The code to obtain gps fixes is the recommended method:

Java
locationManager = (LocationManager) this.getSystemService(Context.
                                                      LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER
                                                        , 0, 0, locationListener);
//
// Define a listener that responds to location updates/
     LocationListener locationListener = new LocationListener() 
     {    
     public void onLocationChanged(Location location) 
     {      
      // Called when a new location is found by the network location provider.      
      makeUseOfNewLocation(location);    
     }//end of location change       
  public void onStatusChanged(String provider, int status, Bundle extras) {}   
     public void onProviderEnabled(String provider) {}   
     public void onProviderDisabled(String provider) {}
   };
   private void makeUseOfNewLocation(Location loc) 
   {
   Location mLoc = (Location) loc;
   
   if(GetReference)  //this location will be the reference
   {
            MapView.latRef = mLoc.getLatitude();
            MapView.lonRef = mLoc.getLongitude();
            GetReference = false; //we have got our reference
   }//end of get reference
   else  //this is a normal location
   {
    WalkersMapGps.Location[WalkersMapGps.NumLoc][0] = mLoc.getLatitude();                                 
    WalkersMapGps.Location[WalkersMapGps.NumLoc][1] = mLoc.getLongitude();
    WalkersMapGps.Location[WalkersMapGps.NumLoc][2] = mLoc.getAltitude();
    WalkersMapGps.Location[WalkersMapGps.NumLoc][3] = mLoc.getTime();
    WalkersMapGps.Location[WalkersMapGps.NumLoc][4] = mLoc.getAccuracy();
    
    if(WalkersMapGps.NumLoc < 574)
    {
    mDbHelper.AddLocation(WalkersMapGps.NumLoc + WalkersMapGps.NumWay,"gps",
          WalkersMapGps.Location[WalkersMapGps.NumLoc][0],
       WalkersMapGps.Location[WalkersMapGps.NumLoc][1],
       WalkersMapGps.Location[WalkersMapGps.NumLoc][2],
       WalkersMapGps.Location[WalkersMapGps.NumLoc][3],
       WalkersMapGps.Location[WalkersMapGps.NumLoc][4]); //add location to sqlite
    WalkersMapGps.NumLoc += 1;     
     } //we have one
    else{
    Toast.makeText(getApplication(), "Only 576 locations allowed", Toast.LENGTH_SHORT).show();  
    }//end of filled buffer
   }//end of get gps location
   cancelGps();  //we are finished
   }//end of location changed 

Set Reference:

Image 7

The selectReference class has the code to select the start or reference point for the map.
The user can enter a latitude/longitude: They can enter each separately.
Android does not have an edit text box which allows negative float numbers.
To implement this feature I use try catch blocks. To catch a value out of bounds
I throw an exception. The latitude entry is shown:

Java
final EditText ReferenceLat = (EditText) findViewById(R.id.enter_latitude);
  ReferenceLat.setText(String.valueOf(MapView.latRef)); //show existing reference
     
   final Button selectLat = (Button) findViewById(R.id.b101);
         selectLat.setOnClickListener(new View.OnClickListener() 
         {      
   public void onClick(View v) {
     try
     {
     Editable text = ReferenceLat.getText();  
     double lat = Double.parseDouble(text.toString());
     if(lat < -80 || lat > 84) {throw new Exception();}//out of range
     MapView.latRef = lat; //enter new reference
     }//end of try
     catch(Exception e){
     Toast.makeText(getApplication(), "Latitude must between -80.0 and 84.0", Toast.LENGTH_SHORT).show(); 
     }//end of catch
    }//end have click
   });////end of click listener

Continuous Gps:

The gps is a heavy user of battery power.

I require a gps fix once every 5 minutes.
The first gps fix requires the download of the
Ephemeris (precise satellite orbit) for each satellite.
This may take up to 2 minutes.
Subsequent fixes will only take seconds.
So the phone sleeps for most of the time and it wakes to get
a quick gps fix.
This uses very little battery, extending the period the user
can utilize this application.

Firstly in public class ContinuousGps I set the alarm manager.

Java
public ContinuousGps(Context context) {
  mContext = context; 
  mAlarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
  Intent i = new Intent(mContext, OnAlarmReceiver.class);
  pi = PendingIntent.getBroadcast(mContext, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
 }// end of setup alarm manager
 public void cancelContinuous(){ mAlarmManager.cancel(pi); }
 
 public void setContinuous(Long taskId, long alarmtime) {
          
        mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 10000,
          alarmtime, pi);
 }

Then in public class OnAlarmReceiver I need to setup a BroadcastReceiver.
When this is received we wake the phone and hold it on while we call
a service.

Java
public class OnAlarmReceiver extends BroadcastReceiver 
{
 @Override
 public void onReceive(Context context, Intent intent) 
 {  
  WakeIntentService.acquireStaticLock(context);       
  Intent i = new Intent(context, GpsService.class);   
  context.startService(i);  
 }//end of on receive method
}//end of alarm receiver class

The service started is in the public class GpsService.

The service starts the GpsActivity which gets the gps fix.

Java
public class GpsService extends WakeIntentService { 
 public GpsService() {
  super("GpsService");
   }
 @Override
 void doGpsWork(Intent intent) 
 {  
  Intent i = new Intent(this, GpsActivity.class);
  i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  startActivity(i);
 }//end of gps work 
}//end of gps service

The WakeIntentService class is where we setup the PowerManager.WakeLock.
The code used is the recommended method.

Java
public abstract class WakeIntentService extends IntentService
{
    abstract void doGpsWork(Intent intent);
 
 public static final String LOCK_NAME_STATIC="com.carl47.walkers";
 private static PowerManager.WakeLock lockStatic=null;
 
 public static void acquireStaticLock(Context context) {
  getLock(context).acquire();
 }
 
 synchronized private static PowerManager.WakeLock getLock(Context context) {
  if (lockStatic==null) {
   PowerManager mgr=(PowerManager)context.getSystemService(Context.POWER_SERVICE);
   lockStatic=mgr.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
              LOCK_NAME_STATIC);
   lockStatic.setReferenceCounted(true);
  }
  return(lockStatic);
 }
 
 public WakeIntentService(String name) {
  super(name);
 }
 
 @Override
 final protected void onHandleIntent(Intent intent) {
  try {
   doGpsWork(intent);
  }
  finally {
   getLock(this).release();
  }
 }
 
}//end of wake intent service

Manifest:

All the displayed screens are set to portrait, the map page has
no title bar and uses the full screen.
The manifest contains all the necessary permissions and activities used:

 <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="<a href="http://schemas.android.com/apk/res/android">http://schemas.android.com/apk/res/android</a>"
    package="carl47.com"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk android:minSdkVersion="6" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> 
    <uses-permission android:name="android.permission.WAKE_LOCK" />   
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".WalkersMapGps"
            android:label="@string/app_name" 
            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" 
            android:screenOrientation="portrait">            
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
         <activity
            android:name=".GpsActivity"
            android:label="@string/app_name"            
            android:screenOrientation="portrait"/>
          <activity
            android:name=".HelpFile"
            android:label="@string/app_name" 
            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" 
            android:screenOrientation="portrait"/>
           <activity 
              android:name=".GpsSelect" 
              android:label="@string/app_name"
              android:screenOrientation="portrait" />
          <activity 
              android:name=".selectReference" 
              android:label="@string/app_name" 
              android:screenOrientation="portrait"/>
          <activity 
              android:name=".ViewLocations" 
              android:label="@string/app_name" 
              android:screenOrientation="portrait"/>
          <activity 
              android:name=".WayPoints" 
              android:label="@string/app_name" 
              android:screenOrientation="portrait"/>          
          <receiver android:name=".OnAlarmReceiver" />
       <service android:name=".GpsService" />  
    </application>
</manifest>

Conclusion:

The application works as per the specifications.

It’s published on the Android market. It’s completely free, download it to your Android phone
to see it work as advertised.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Australia Australia
I identify with the starfish.
I may be really stupid and have to use visual basic but at least I'm happy.

Comments and Discussions

 
Questionnice one Pin
Jaydeep Jadav15-Jun-12 3:46
Jaydeep Jadav15-Jun-12 3:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.