/* * Copyright (C) 2012 The Serval Project * * This file is part of the Serval Maps Software * * Serval Maps Software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This source code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this source code; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.servalproject.maps; import java.io.File; import java.util.ArrayList; import org.mapsforge.android.maps.overlay.ArrayWayOverlay; import org.mapsforge.core.GeoPoint; import org.mapsforge.android.maps.overlay.ItemizedOverlay; import org.mapsforge.android.maps.MapView; import org.mapsforge.android.maps.overlay.OverlayWay; import org.servalproject.maps.location.LocationCollector; import org.servalproject.maps.mapsforge.NewPoiOverlay; import org.servalproject.maps.mapsforge.OverlayItem; import org.servalproject.maps.mapsforge.OverlayItems; import org.servalproject.maps.mapsforge.OverlayList; import org.servalproject.maps.provider.LocationsContract; import org.servalproject.maps.provider.PointsOfInterestContract; import org.servalproject.maps.utils.FileUtils; import android.content.ContentResolver; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.location.Location; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.preference.PreferenceManager; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.widget.Toast; /** * An activity to show a map */ public class MapActivity extends org.mapsforge.android.maps.MapActivity { /* * public class level constants */ /* * private class level constants */ private final boolean V_LOG = false; private final String TAG = "MapActivity"; /* * private class level variables */ private Intent coreServiceIntent; private Handler updateHandler = new Handler(); // number of seconds to delay between map updates private int defaultUpdateDelay = 10 * 1000; private volatile int updateDelay = defaultUpdateDelay; private volatile boolean keepCentered = false; private long defaultPoiMaxAge = 43200 * 1000; private volatile long poiMaxAge = defaultPoiMaxAge; private long defaultLocationMaxAge = 43200 * 1000; private volatile long locationMaxAge = defaultLocationMaxAge; private SharedPreferences preferences = null; // drawables for marker icons private Drawable peerLocationMarker; private Drawable selfLocationMarker; private Drawable poiLocationMarker; // list of markers private OverlayList overlayList; private MapView mapView; private ArrayWayOverlay arrayWayOverlay = null; // phone number and sid private String meshPhoneNumber = null; // check to know if a map update is running private volatile boolean updateRunning = false; /* * (non-Javadoc) * @see android.app.Activity#onCreate(android.os.Bundle) */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.map); // start the core service coreServiceIntent = new Intent(this, org.servalproject.maps.services.CoreService.class); startService(coreServiceIntent); // get the map data file name Bundle mBundle = this.getIntent().getExtras(); String mMapFileName = null; if(mBundle != null) { mMapFileName = mBundle.getString("mapFileName"); } else { // finish if there is no file name finish(); } // instantiate mapsforge classes mapView = new MapView(this); mapView.setClickable(true); mapView.setBuiltInZoomControls(true); if(mMapFileName != null) { if(FileUtils.isFileReadable(mMapFileName) == false) { String mMapDataPath = Environment.getExternalStorageDirectory().getPath(); mMapDataPath += getString(R.string.system_path_map_data); mMapFileName = mMapDataPath + mMapFileName; if (FileUtils.isFileReadable(mMapFileName)) mapView.setMapFile(new File(mMapFileName)); } else { mapView.setMapFile(new File(mMapFileName)); } } setContentView(mapView); // get the drawables for the markers peerLocationMarker = ItemizedOverlay.boundCenterBottom(getResources().getDrawable(R.drawable.peer_location)); selfLocationMarker = ItemizedOverlay.boundCenterBottom(getResources().getDrawable(R.drawable.peer_location_self)); poiLocationMarker = ItemizedOverlay.boundCenterBottom(getResources().getDrawable(R.drawable.incident_marker)); overlayList = new OverlayList(poiLocationMarker, this); mapView.getOverlays().add(overlayList); // add the long press detecting overlay for adding new POIs mapView.getOverlays().add(new NewPoiOverlay(this)); // get the preferences preferences = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); // set the update delay String mPreference = preferences.getString("preferences_map_update_interval", null); if(mPreference == null) { updateDelay = defaultUpdateDelay; } else { updateDelay = Integer.parseInt(mPreference); } // get the max poi and location age preferences mPreference = preferences.getString("preferences_map_max_poi_age", null); if(mPreference != null) { poiMaxAge = Long.parseLong(mPreference) * 1000; } mPreference = preferences.getString("preferences_map_max_location_age", null); if(mPreference != null) { locationMaxAge = Long.parseLong(mPreference) * 1000; } // set flag to keep the map centered keepCentered = preferences.getBoolean("preferences_map_follow", false); // determine if we need to show the users GPS trace if(preferences.getBoolean("preferences_map_show_track", false)) { // will be drawing a line not a polygon at this stage Paint mDefaultFill = null; Paint mDefaultLine = new Paint(Paint.ANTI_ALIAS_FLAG); mDefaultLine.setARGB(255, 85, 140, 248); mDefaultLine.setStyle(Paint.Style.STROKE); mDefaultLine.setStrokeWidth(2); arrayWayOverlay = new ArrayWayOverlay(mDefaultFill, mDefaultLine); mapView.getOverlays().add(arrayWayOverlay); } // listen for changes in the preferences preferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); // get the phone number and sid ServalMaps mApplication = (ServalMaps) getApplication(); meshPhoneNumber = mApplication.getPhoneNumber(); mApplication = null; // update the map without delay updateHandler.post(updateMapTask); if(V_LOG) { Log.v(TAG, "activity created"); } } /* * object to listen for changes in the preferences */ private SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() { /* * (non-Javadoc) * @see android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged(android.content.SharedPreferences, java.lang.String) */ @Override public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { if(V_LOG) { Log.v(TAG, "a change in shared preferences has been deteceted"); Log.v(TAG, "preference change: '" + key + "'"); } if(key.equals("preferences_map_update_interval") == true) { String mPreference = preferences.getString("preferences_map_update_interval", null); if(mPreference != null) { updateDelay = Integer.parseInt(mPreference); } if(V_LOG) { Log.v(TAG, "new map update delay is '" + updateDelay + "'"); } } else if(key.equals("preferences_map_follow") == true) { keepCentered = preferences.getBoolean("preferences_category_map", false); if(V_LOG) { if(keepCentered) { Log.v(TAG, "map will keep centered on the users location"); } else { Log.v(TAG, "map will not keep centered on the users location"); } } } else if(key.equals("preferences_map_max_poi_age") == true) { String mPreference = preferences.getString("preferences_map_max_poi_age", null); if(mPreference != null) { poiMaxAge = Long.parseLong(mPreference) * 1000; } if(V_LOG) { Log.v(TAG, "new max POI age is '" + poiMaxAge + "'"); } } else if(key.equals("preferences_map_max_location_age") == true) { String mPreference = preferences.getString("preferences_map_max_location_age", null); if(mPreference != null) { locationMaxAge = Long.parseLong(mPreference) * 1000; } if(V_LOG) { Log.v(TAG, "new max location age is '" + locationMaxAge + "'"); } } else if(key.equals("preferences_map_show_track") == true) { // add the way overlay // TODO add appropriate constants / private methods if(preferences.getBoolean("preferences_map_show_track", false)) { // will be drawing a line not a polygon at this stage Paint mDefaultFill = null; Paint mDefaultLine = new Paint(Paint.ANTI_ALIAS_FLAG); mDefaultLine.setARGB(255, 85, 140, 248); mDefaultLine.setStyle(Paint.Style.STROKE); mDefaultLine.setStrokeWidth(2); arrayWayOverlay = new ArrayWayOverlay(mDefaultFill, mDefaultLine); mapView.getOverlays().add(arrayWayOverlay); } else { mapView.getOverlays().remove(arrayWayOverlay); arrayWayOverlay.clear(); arrayWayOverlay = null; } } } }; /* * create the menu * * (non-Javadoc) * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu) */ @Override public boolean onCreateOptionsMenu(Menu menu) { // inflate the menu based on the XML MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.map_activity, menu); return true; } /* * handle click events from the menu * * (non-Javadoc) * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem) */ @Override public boolean onOptionsItemSelected(MenuItem item) { Intent mIntent; switch(item.getItemId()){ case R.id.menu_map_activity_preferences: // show the preferences activity mIntent = new Intent(this, org.servalproject.maps.SettingsActivity.class); startActivity(mIntent); return true; case R.id.menu_map_activity_add_poi: // show the add POI activity mIntent = new Intent(this, org.servalproject.maps.NewPoiActivity.class); startActivity(mIntent); return true; case R.id.menu_map_activity_centre_map: // recentre the map on the current location Location mLocation = LocationCollector.getLocation(); if(mLocation != null) { GeoPoint mGeoPoint = new GeoPoint(mLocation.getLatitude(), mLocation.getLongitude()); mapView.getController().setCenter(mGeoPoint); } else { Toast.makeText(getApplicationContext(), R.string.map_ui_toast_location_unavailable, Toast.LENGTH_LONG).show(); } return true; case R.id.menu_map_activity_poi_list: // show the list of poi mIntent = new Intent(this, org.servalproject.maps.PoiListActivity.class); startActivity(mIntent); return true; case R.id.menu_map_activity_help_about: // show the help text mIntent = new Intent(this, org.servalproject.maps.AboutActivity.class); startActivity(mIntent); return true; case R.id.menu_map_activity_close: // close this activity finish(); return true; default: return super.onOptionsItemSelected(item); } } /* * (non-Javadoc) * @see org.mapsforge.android.maps.MapActivity#onDestroy() */ @Override public void onDestroy() { // stop the core service stopService(coreServiceIntent); // stop the handle / runnable looping action updateHandler.removeCallbacks(updateMapTask); super.onDestroy(); if(V_LOG) { Log.v(TAG, "activity destroyed"); } } /* * (non-Javadoc) * @see org.mapsforge.android.maps.MapActivity#onPause() */ @Override public void onPause() { // stop the updating of the map updateHandler.removeCallbacks(updateMapTask); super.onPause(); } /* * (non-Javadoc) * @see org.mapsforge.android.maps.MapActivity#onResume() */ public void onResume() { // restart the updating of the map updateHandler.post(updateMapTask); super.onResume(); } /* * (non-Javadoc) * @see android.app.Activity#onActivityResult(int, int, android.content.Intent) */ @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { } /* * methods and variables used to update the map */ // task used to update the map ui with new markers private Runnable updateMapTask = new Runnable() { public void run() { if(V_LOG){ Log.v(TAG, "update map task running"); } if(updateRunning) { // an update is already running so just return return; } // indicate the map update is underway updateRunning = true; // resolve the content uri ContentResolver mContentResolver = getApplicationContext().getContentResolver(); // get the location marker content Cursor mCursor = mContentResolver.query(LocationsContract.LATEST_CONTENT_URI, null, null, null, null); // store the list of items ArrayList<OverlayItem> mItems = new ArrayList<OverlayItem>(); if(mCursor == null) { Log.i(TAG, "a null cursor was returned when looking up location info"); return; } if(V_LOG) { Log.v(TAG, "rows in location info cursor: " + mCursor.getCount()); } if(mCursor.getCount() > 0) { // process the location records GeoPoint mGeoPoint; String mPhoneNumber; OverlayItem mOverlayItem; long mLocationAge; long mCompareTime = System.currentTimeMillis() - locationMaxAge; while(mCursor.moveToNext()) { // check on the age of the info if required if(locationMaxAge != -1000) { mLocationAge = mCursor.getLong(mCursor.getColumnIndex(LocationsContract.Table.TIMESTAMP)); if(mLocationAge < mCompareTime) { // skip this record continue; } } // get the basic information mPhoneNumber = mCursor.getString(mCursor.getColumnIndex(LocationsContract.Table.PHONE_NUMBER)); // get the geographic coordinates mGeoPoint = new GeoPoint(mCursor.getDouble(mCursor.getColumnIndex(LocationsContract.Table.LATITUDE)), mCursor.getDouble(mCursor.getColumnIndex(LocationsContract.Table.LONGITUDE))); // determine what type of marker to create if(mPhoneNumber.equals(meshPhoneNumber) == true) { // this is a self marker mOverlayItem = new OverlayItem(mGeoPoint, null, null, selfLocationMarker); mOverlayItem.setType(OverlayItems.SELF_LOCATION_ITEM); mOverlayItem.setRecordId(mCursor.getInt(mCursor.getColumnIndex(LocationsContract.Table._ID))); // recenter the map if required if(keepCentered) { mapView.getController().setCenter(mGeoPoint); if(V_LOG) { Log.v(TAG, "map was recentered"); } } } else { // this is a peer marker mOverlayItem = new OverlayItem(mGeoPoint, null, null, peerLocationMarker); mOverlayItem.setType(OverlayItems.PEER_LOCATION_ITEM); mOverlayItem.setRecordId(mCursor.getInt(mCursor.getColumnIndex(LocationsContract.Table._ID))); } mItems.add(mOverlayItem); } } // play nice and tidy up mCursor.close(); // get the POI content String[] mProjection = new String[3]; mProjection[0] = PointsOfInterestContract.Table._ID; mProjection[1] = PointsOfInterestContract.Table.LATITUDE; mProjection[2] = PointsOfInterestContract.Table.LONGITUDE; // determine if we need to restrict the list of POIs String mSelection = null; String[] mSelectionArgs = null; // restrict the poi content returned if required if(poiMaxAge != -1000) { mSelection = PointsOfInterestContract.Table.TIMESTAMP + " > ? "; mSelectionArgs = new String[1]; mSelectionArgs[0] = Long.toString(System.currentTimeMillis() - poiMaxAge); } mCursor = mContentResolver.query( PointsOfInterestContract.CONTENT_URI, mProjection, mSelection, mSelectionArgs, null); if(mCursor == null) { Log.i(TAG, "a null cursor was returned when looking up POI info"); return; } if(V_LOG) { Log.v(TAG, "rows in POI cursor: " + mCursor.getCount()); } // process the list of poi records if(mCursor.getCount() > 0) { // process the location records GeoPoint mGeoPoint; OverlayItem mOverlayItem; while(mCursor.moveToNext()) { // get the geographic coordinates mGeoPoint = new GeoPoint(mCursor.getDouble(mCursor.getColumnIndex(PointsOfInterestContract.Table.LATITUDE)), mCursor.getDouble(mCursor.getColumnIndex(PointsOfInterestContract.Table.LONGITUDE))); mOverlayItem = new OverlayItem(mGeoPoint, null, null, poiLocationMarker); mOverlayItem.setType(OverlayItems.POI_ITEM); mOverlayItem.setRecordId(mCursor.getInt(mCursor.getColumnIndex(PointsOfInterestContract.Table._ID))); mItems.add(mOverlayItem); } } // play nice and tidy up mCursor.close(); // build the gps track overlay if required if(arrayWayOverlay != null) { // determine which fields to return mProjection = new String[2]; mProjection[0] = LocationsContract.Table.LATITUDE; mProjection[1] = LocationsContract.Table.LONGITUDE; // check if we need to take into account the age of the information if(locationMaxAge != -1000) { mSelection = LocationsContract.Table.PHONE_NUMBER + " = ? AND " + LocationsContract.Table.TIMESTAMP + " > ?"; mSelectionArgs = new String[2]; mSelectionArgs[0] = meshPhoneNumber; mSelectionArgs[1] = Long.toString(System.currentTimeMillis() - locationMaxAge); } else { mSelection = LocationsContract.Table.PHONE_NUMBER + " = ?"; mSelectionArgs = new String[1]; mSelectionArgs[0] = meshPhoneNumber; } // get the data mCursor = mContentResolver.query( LocationsContract.CONTENT_URI, mProjection, mSelection, mSelectionArgs, LocationsContract.Table.TIMESTAMP); if(mCursor.getCount() > 0) { if(V_LOG) { Log.v(TAG, "gps track contains: '" + mCursor.getCount() + "' points"); } // declare array to hold our list of points GeoPoint[][] mWayPoints = new GeoPoint[1][mCursor.getCount()]; int mCount = 0; GeoPoint mGeoPoint; // populate the array while(mCursor.moveToNext()) { mGeoPoint = new GeoPoint(mCursor.getDouble(mCursor.getColumnIndex(LocationsContract.Table.LATITUDE)), mCursor.getDouble(mCursor.getColumnIndex(LocationsContract.Table.LONGITUDE))); mWayPoints[0][mCount] = mGeoPoint; mCount++; } // play nice and tidy up mCursor.close(); OverlayWay mOverlayWay = new OverlayWay(mWayPoints, null, null); // update the overlay arrayWayOverlay.clear(); arrayWayOverlay.addWay(mOverlayWay); } } // update and redraw the overlay overlayList.clear(); overlayList.addItems(mItems); overlayList.requestRedraw(); if(arrayWayOverlay != null) { arrayWayOverlay.requestRedraw(); } // indicate that a map update is finished updateRunning = false; // add the task back onto the queue updateHandler.postDelayed(updateMapTask, updateDelay); } }; }