当用户靠近特定位置时向用户发送警报
Sending alert to user when he is near to a specific location
我正在开发一个应用程序,当用户在特定位置 200 米范围内时必须向用户发送通知。
我的用户是汽车司机。当我使用 Google 地理围栏 API 并在我开车时对其进行测试时,有时会有很大的延迟,因为它会在我超过范围后向我发送通知。
我考虑过每 3 秒添加一个位置跟踪器并计算从用户当前位置到想要的位置的距离,如果距离小于 200 米我会发送通知。
有人知道任何其他解决方案或 API 可以处理它吗?
这是GeoFencing
代码
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()) {
}
}
}
和 GeofenceTransitionsIntentService
服务。
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());
}
}
首先,除非您愿意向 Google 付费使用他们的 API,否则我强烈建议您改用 OSMDroid 库进行开发。
如果您想要线性距离(半径)而不是多边形位置检测,那么地理围栏就太过分了,这会让您在电池使用和设备温度方面付出高昂的代价。
确定从目标位置到所需位置的直线距离很容易。您可以使用此代码,例如:
public double distanceGeoPoints (GeoPoint geoPoint01, GeoPoint geoPoint02) {
double lat1 = geoPoint01.getLatitudeE6()/1E6;
double lng1 = geoPoint01.getLongitudeE6()/1E6;
double lat2 = geoPoint02.getLatitudeE6()/1E6;
double lng2 = geoPoint02.getLongitudeE6()/1E6;
double earthRadius = 3958.75;
double dLat = Math.toRadians(lat2-lat1);
double dLng = Math.toRadians(lng2-lng1);
double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
Math.sin(dLng/2) * Math.sin(dLng/2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
double dist = earthRadius * c;
int meterConversion = 1609;
double geopointDistance = dist * meterConversion;
return geopointDistance;
}
这是 Haversine 公式,通常被接受为 "precise enough for most intents and purposes"。你要明白,地球不是一个完美的球体,经过大爸爸反复练习击球,地球更像是一个棒球。
从我从您的应用程序中看到的情况来看,这应该可以为您提供必要的精度。但是如果你好奇的话,read more。
我正在开发一个应用程序,当用户在特定位置 200 米范围内时必须向用户发送通知。
我的用户是汽车司机。当我使用 Google 地理围栏 API 并在我开车时对其进行测试时,有时会有很大的延迟,因为它会在我超过范围后向我发送通知。
我考虑过每 3 秒添加一个位置跟踪器并计算从用户当前位置到想要的位置的距离,如果距离小于 200 米我会发送通知。
有人知道任何其他解决方案或 API 可以处理它吗?
这是GeoFencing
代码
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()) {
}
}
}
和 GeofenceTransitionsIntentService
服务。
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());
}
}
首先,除非您愿意向 Google 付费使用他们的 API,否则我强烈建议您改用 OSMDroid 库进行开发。
如果您想要线性距离(半径)而不是多边形位置检测,那么地理围栏就太过分了,这会让您在电池使用和设备温度方面付出高昂的代价。
确定从目标位置到所需位置的直线距离很容易。您可以使用此代码,例如:
public double distanceGeoPoints (GeoPoint geoPoint01, GeoPoint geoPoint02) {
double lat1 = geoPoint01.getLatitudeE6()/1E6;
double lng1 = geoPoint01.getLongitudeE6()/1E6;
double lat2 = geoPoint02.getLatitudeE6()/1E6;
double lng2 = geoPoint02.getLongitudeE6()/1E6;
double earthRadius = 3958.75;
double dLat = Math.toRadians(lat2-lat1);
double dLng = Math.toRadians(lng2-lng1);
double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
Math.sin(dLng/2) * Math.sin(dLng/2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
double dist = earthRadius * c;
int meterConversion = 1609;
double geopointDistance = dist * meterConversion;
return geopointDistance;
}
这是 Haversine 公式,通常被接受为 "precise enough for most intents and purposes"。你要明白,地球不是一个完美的球体,经过大爸爸反复练习击球,地球更像是一个棒球。
从我从您的应用程序中看到的情况来看,这应该可以为您提供必要的精度。但是如果你好奇的话,read more。