I'm developing an app that must send a notification to users when they are in range of 200m of a specific location.
My users are car drivers. When I used Google Geofencing API and tested it while I was driving, there was a big delay sometimes as it was sending me the notification after I passed the range.
I thought about adding a location tracker every 3 seconds and calculate the distance from user current location to the wanted location and if the distance is less than 200m I would send a notification.
Anyone knows any other solution or an API that might handle it?
Here is the GeoFencing
Code
public class MapsActivity extends FragmentActivity implements OnMapReadyCallback, GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
ResultCallback<Status>{
@BindView(R.id.tvLocation)
MatabTextView tvLocation;
ProgressBar progressBar;
WaveFormView waveFormView;
protected ArrayList<Geofence> mGeofenceList;
protected GoogleApiClient mGoogleApiClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
ButterKnife.bind(this);
waveFormView = (WaveFormView) findViewById(R.id.Wave);
waveFormView.updateAmplitude(0.05f, true);
waveFormView.updateAmplitude(0.1f, true);
waveFormView.updateAmplitude(0.2f, true);
waveFormView.updateAmplitude(0.5f, true);
StrictMode.ThreadPolicy old = StrictMode.getThreadPolicy();
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(old)
.permitDiskWrites()
.build());
StrictMode.setThreadPolicy(old);
progressBar = (ProgressBar) findViewById(R.id.progress);
progressBar.setVisibility(View.VISIBLE);
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
mGeofenceList = new ArrayList<Geofence>();
populateGeofenceList();
buildGoogleApiClient();
}
@Override
protected void onStart() {
super.onStart();
if (!mGoogleApiClient.isConnecting() || !mGoogleApiClient.isConnected()) {
mGoogleApiClient.connect();
}
}
public void addGeofencesButtonHandler(View view) {
if (!mGoogleApiClient.isConnected()) {
Toast.makeText(this, "Google API Client not connected!", Toast.LENGTH_SHORT).show();
return;
}
try {
LocationServices.GeofencingApi.addGeofences(
mGoogleApiClient,
getGeofencingRequest(),
getGeofencePendingIntent()
).setResultCallback(this); // Result processed in onResult().
} catch (SecurityException securityException) {
// Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission.
}
}
private GeofencingRequest getGeofencingRequest() {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER | GeofencingRequest.INITIAL_TRIGGER_EXIT);
builder.addGeofences(mGeofenceList);
return builder.build();
}
private PendingIntent getGeofencePendingIntent() {
Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling addgeoFences()
return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
public void onResult(Status status) {
if (status.isSuccess()) {
Toast.makeText(
this,
"Geofences Added",
Toast.LENGTH_SHORT
).show();
} else {
String errorMessage = GeofenceErrorMessages.getErrorString(this,
status.getStatusCode());
}
}
@Override
protected void onStop() {
super.onStop();
if (mGoogleApiClient.isConnecting() || mGoogleApiClient.isConnected()) {
mGoogleApiClient.disconnect();
}
}
@Override
public void onConnected(Bundle connectionHint) {
}
@Override
public void onConnectionFailed(ConnectionResult result) {
// Do something with result.getErrorCode());
Log.d("Geofencing", String.valueOf(result.getErrorCode()));
}
@Override
public void onMapReady(GoogleMap googleMap) {
}
@Override
public void onConnectionSuspended(int cause) {
mGoogleApiClient.connect();
}
protected synchronized void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}
public void populateGeofenceList() {
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("roads").child("Name").child("locations");
myRef.addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
progressBar.setVisibility(View.GONE);
mGeofenceList.add(new Geofence.Builder()
.setRequestId(dataSnapshot.getKey())
.setCircularRegion(
(Double) dataSnapshot.child("lat").getValue(),
(Double) dataSnapshot.child("lang").getValue(),
Constants.GEOFENCE_RADIUS_IN_METERS
)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER
)
.build());
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
});
for (Map.Entry<String, LatLng> entry : Constants.LANDMARKS.entrySet()) {
}
}
}
And the GeofenceTransitionsIntentService
service.
public class GeofenceTransitionsIntentService extends IntentService {
protected static final String TAG = "GeofenceTransitionsIS";
public GeofenceTransitionsIntentService() {
super(TAG); // use TAG to name the IntentService worker thread
}
@Override
protected void onHandleIntent(Intent intent) {
GeofencingEvent event = GeofencingEvent.fromIntent(intent);
String description = getGeofenceTransitionDetails(event);
sendNotification(description);
if (event.hasError()) {
Log.e(TAG, "GeofencingEvent Error: " + event.getErrorCode());
return;
}
}
private static String getGeofenceTransitionDetails(GeofencingEvent event) {
String transitionString =
GeofenceStatusCodes.getStatusCodeString(event.getGeofenceTransition());
List triggeringIDs = new ArrayList();
for (Geofence geofence : event.getTriggeringGeofences()) {
triggeringIDs.add(geofence.getRequestId());
}
return String.format("%s: %s", transitionString, TextUtils.join(", ", triggeringIDs));
}
private void sendNotification(String notificationDetails) {
// Create an explicit content Intent that starts MainActivity.
Intent notificationIntent = new Intent(getApplicationContext(), MapsActivity.class);
// Get a PendingIntent containing the entire back stack.
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(MapsActivity.class).addNextIntent(notificationIntent);
PendingIntent notificationPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Get a notification builder that's compatible with platform versions >= 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
Uri alarmSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
// Define the notification settings.
builder.setColor(Color.RED)
.setContentTitle(notificationDetails)
.setSound(alarmSound)
.setContentText("Click notification to return to App")
.setContentIntent(notificationPendingIntent)
.setSmallIcon(R.mipmap.ic_launcher)
.setAutoCancel(true);
// Fire and notify the built Notification.
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0, builder.build());
}
}
Firstly, unless you are willing to pay a fee to Google for using their API, i strongly recommend you to develop using the OSMDroid library instead.
If you want linear distance (radius) and not polygon location detection, geofencing is overkill which will cost you dearly in battery use and device temperature.
Determining the linear distance from your target's position to the desired location is easy. You may use this code, for example:
This is the Haversine Formula, generally accepted as "precise enough for most intents and purposes". You must understand that the Earth is not a perfect sphere, it is more like a baseball after Big Pappy uses it repeatedly for batting practice.
From what i see from your app, this should give you the necessary precision. But do read more if you are curious.