POST
|
'com.esri.arcgisruntime:arcgis-android:100.14.0' has a dependency 'org.conscrypt:conscrypt-openjdk-uber:2.2.1'. This dependency is incorrect as it should only be used for windows, linux and MacOS. The following github repository mentions that for Android use conscrypt-android. https://github.com/google/conscrypt#android 'org.conscrypt:conscrypt-android:2.5.2' This has caused me a lot of pain with a custom react native component as the app kept crashing when it was unable to find the required libraries. The solution I have used until this is fixed in the package repository is to exclude the "conscrypt-openjdk-uber" and add conscrypt-android as a dependency. implementation ('com.esri.arcgisruntime:arcgis-android:100.14.1') {
exclude group: "org.conscrypt", module: "conscrypt-openjdk-uber"
}
implementation 'org.conscrypt:conscrypt-android:2.5.2'
... View more
08-10-2022
05:44 AM
|
0
|
1
|
382
|
POST
|
Thank you for pointing that out to me that was a stupid mistake. After adding the parameter the application started working. The error message that I was getting was not not very helpful as it said the "Arcade expression is invalid.". It would have been better if it gave the reason why it is invalid. I have never used Arcade expressions before.
... View more
07-01-2022
03:52 AM
|
0
|
0
|
560
|
POST
|
I have a database with points data. Each has a group and points in a group represent a boundary (polygon) on the map. I am trying to use these boundaries with Geotriggers to alert the users when they move in and out of boundaries. I have used the location driven Geotriggers sample from git hub to get an idea on how to achieve this . https://github.com/Esri/arcgis-runtime-samples-android/tree/main/kotlin/set-up-location-driven-geotriggers The only difference is that the sample uses portal items and the data I have is a set of points. So instead of using portal items I create a feature collection with polygon table with a single geometry in the shape of a square. On running the application I do not see alerts at all when the simulated location source moves in and out of the boundary. I have included the code below so it is easier to understand what I am doing. Is it possible to implement this feature with polygon feature tables? Are we able to use Geotriggers without showing a map to the user? package uk.co.deloitte.foregroundservice;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;
import com.esri.arcgisruntime.ArcGISRuntimeEnvironment;
import com.esri.arcgisruntime.arcade.ArcadeExpression;
import com.esri.arcgisruntime.data.Feature;
import com.esri.arcgisruntime.data.FeatureCollection;
import com.esri.arcgisruntime.data.FeatureCollectionTable;
import com.esri.arcgisruntime.data.Field;
import com.esri.arcgisruntime.geometry.GeometryType;
import com.esri.arcgisruntime.geometry.Point;
import com.esri.arcgisruntime.geometry.PolygonBuilder;
import com.esri.arcgisruntime.geometry.Polyline;
import com.esri.arcgisruntime.geometry.SpatialReferences;
import com.esri.arcgisruntime.geotriggers.FeatureFenceParameters;
import com.esri.arcgisruntime.geotriggers.FenceGeotrigger;
import com.esri.arcgisruntime.geotriggers.FenceGeotriggerNotificationInfo;
import com.esri.arcgisruntime.geotriggers.FenceNotificationType;
import com.esri.arcgisruntime.geotriggers.FenceRuleType;
import com.esri.arcgisruntime.geotriggers.GeotriggerMonitor;
import com.esri.arcgisruntime.geotriggers.GeotriggerMonitorNotificationEvent;
import com.esri.arcgisruntime.geotriggers.GeotriggerMonitorNotificationEventListener;
import com.esri.arcgisruntime.geotriggers.GeotriggerNotificationInfo;
import com.esri.arcgisruntime.geotriggers.LocationGeotriggerFeed;
import com.esri.arcgisruntime.layers.FeatureCollectionLayer;
import com.esri.arcgisruntime.location.SimulatedLocationDataSource;
import com.esri.arcgisruntime.location.SimulationParameters;
import com.esri.arcgisruntime.mapping.ArcGISMap;
import com.esri.arcgisruntime.mapping.BasemapStyle;
import com.esri.arcgisruntime.mapping.Viewpoint;
import com.esri.arcgisruntime.mapping.view.LocationDisplay;
import com.esri.arcgisruntime.mapping.view.MapView;
import com.esri.arcgisruntime.symbology.SimpleFillSymbol;
import com.esri.arcgisruntime.symbology.SimpleLineSymbol;
import com.esri.arcgisruntime.symbology.SimpleRenderer;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
private Button btnStartService;
private Button btnStopService;
private Button btnDisplayLocation;
private MapView mMapView;
private LocationDisplay mLocationDisplay;
private GeotriggerMonitor mRoadGeotriggerMonitor;
private final int requestCode = 2;
private final String[] reqPermissions = { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission
.ACCESS_COARSE_LOCATION };
private static final String MAIN_ACTIVITY = "MainActivity";
public static final String CHANNEL_ID = "MainActivityChannel";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnStartService = findViewById(R.id.buttonStartService);
btnStopService = findViewById(R.id.buttonStopService);
btnDisplayLocation = findViewById(R.id.buttonDisplayLocation);
btnStartService.setOnClickListener(view -> startService());
btnStopService.setOnClickListener(view -> stopService());
btnDisplayLocation.setOnClickListener(view -> toggleLocationDisplay());
ArcGISRuntimeEnvironment.setApiKey(BuildConfig.API_KEY);
// inflate MapView from layout
mMapView = findViewById(R.id.mapView);
// initialize map with basemap
ArcGISMap map = new ArcGISMap(BasemapStyle.ARCGIS_IMAGERY);
// assign map to the map view
mMapView.setMap(map);
mMapView.setViewpoint(new Viewpoint( 54.518502, -6.0873785, 5000));
// create feature collection and add to the map as a layer
FeatureCollection featureCollection = new FeatureCollection();
FeatureCollectionLayer featureCollectionLayer = new FeatureCollectionLayer(featureCollection);
map.getOperationalLayers().add(featureCollectionLayer);
// add point, line, and polygon geometry to feature collection
createPolygonTables(featureCollection);
// get the MapView's LocationDisplay
mLocationDisplay = mMapView.getLocationDisplay();
// Listen to changes in the status of the location data source.
mLocationDisplay.addDataSourceStatusChangedListener(dataSourceStatusChangedEvent -> {
// If LocationDisplay started OK, then continue.
if (dataSourceStatusChangedEvent.isStarted())
return;
// No error is reported, then continue.
if (dataSourceStatusChangedEvent.getError() == null)
return;
// If an error is found, handle the failure to start.
// Check permissions to see if failure may be due to lack of permissions.
boolean permissionCheck1 = ContextCompat.checkSelfPermission(this, reqPermissions[0]) ==
PackageManager.PERMISSION_GRANTED;
boolean permissionCheck2 = ContextCompat.checkSelfPermission(this, reqPermissions[1]) ==
PackageManager.PERMISSION_GRANTED;
if (!(permissionCheck1 && permissionCheck2)) {
// If permissions are not already granted, request permission from the user.
ActivityCompat.requestPermissions(this, reqPermissions, requestCode);
Log.i(MAIN_ACTIVITY, "Location Permission granted");
} else {
// Report other unknown failure types to the user - for example, location services may not
// be enabled on the device.
String message = String.format("Error in DataSourceStatusChangedListener: %s", dataSourceStatusChangedEvent
.getSource().getLocationDataSource().getError().getMessage());
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
});
LocationGeotriggerFeed locationGeotriggerFeed = initializeSimulatedLocationDisplay();
// create feature collection and add to the map as a layer
Log.d(MAIN_ACTIVITY, "Feature table size " + featureCollection.getTables().size());
mRoadGeotriggerMonitor = createGeotriggerMonitor(featureCollection.getTables().get(0), 5.0, "BOUNDARY", locationGeotriggerFeed);
}
/**
* Initialize a simulation using a simulated data source and then
* feed it to the [LocationGeotriggerFeed]
*/
private LocationGeotriggerFeed initializeSimulatedLocationDisplay() {
SimulatedLocationDataSource simulatedLocationDataSource = new SimulatedLocationDataSource();
// Create SimulationParameters starting at the current time, a velocity of 10 m/s, and a horizontal and vertical accuracy of 0.0
SimulationParameters simulationParameters = new SimulationParameters(Calendar.getInstance(),
3.0,
0.0,
0.0);
// Use the polyline as defined above or from this ArcGIS Online GeoJSON to define the path.
simulatedLocationDataSource.setLocations(
(Polyline) Polyline.fromJson(getString(R.string.polyline_json)),
simulationParameters);
// Set map to simulate the location data source
simulatedLocationDataSource.startAsync();
// Set map to simulate the location data source
mLocationDisplay.setLocationDataSource(simulatedLocationDataSource);
mLocationDisplay.setAutoPanMode(LocationDisplay.AutoPanMode.RECENTER);
mLocationDisplay.setInitialZoomScale(1000.0);
mLocationDisplay.startAsync();
// LocationGeotriggerFeed will be used in instantiating a FenceGeotrigger in createGeotriggerMonitor()
return new LocationGeotriggerFeed(simulatedLocationDataSource);
}
private void toggleLocationDisplay() {
if (!mLocationDisplay.isStarted()) {
mLocationDisplay.startAsync();
Log.i(MAIN_ACTIVITY, "Location display started.");
} else {
mLocationDisplay.stop();
Log.i(MAIN_ACTIVITY, "Location display stopped.");
}
}
public void startService() {
Log.i("ForegroundService", "Staring Service!");
Intent serviceIntent = new Intent(this, ForegroundService.class);
serviceIntent.putExtra("inputExtra", "Foreground Service Example in Android");
Context context = getApplicationContext();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent);
} else {
ContextCompat.startForegroundService(this, serviceIntent);
}
}
public void stopService() {
Log.i("ForegroundService", "Stopping Service!");
Intent serviceIntent = new Intent(this, ForegroundService.class);
stopService(serviceIntent);
}
/**
* Creates a Polygon Feature Collection Table with one Polygon and adds it to the Feature collection that was passed.
*
* @param featureCollection that the polygon Feature Collection Table will be added to
*/
private void createPolygonTables(FeatureCollection featureCollection) {
// defines the schema for the geometry's attribute
List<Field> polygonFields = new ArrayList<>();
polygonFields.add(Field.createString("Area", "Area Name", 50));
// a feature collection table that creates polygon geometry
FeatureCollectionTable polygonTable = new FeatureCollectionTable(polygonFields, GeometryType.POLYGON, SpatialReferences.getWgs84());
// set a default symbol for features in the collection table
SimpleLineSymbol lineSymbol = new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, 0xFF0000FF, 2);
SimpleFillSymbol fillSymbol = new SimpleFillSymbol(SimpleFillSymbol.Style.BACKWARD_DIAGONAL, 0xFF00FFFF, lineSymbol);
SimpleRenderer renderer = new SimpleRenderer(fillSymbol);
polygonTable.setRenderer(renderer);
// add feature collection table to feature collection
featureCollection.getTables().add(polygonTable);
// create feature using the collection table by passing an attribute and geometry
Map<String, Object> attributes = new HashMap<>();
attributes.put(polygonFields.get(0).getName(), "Restricted area");
PolygonBuilder builder = new PolygonBuilder(SpatialReferences.getWgs84());
builder.addPoint(new Point(-6.0875805, 54.518327));
builder.addPoint(new Point(-6.0871674, 54.518326));
builder.addPoint(new Point(-6.0871694, 54.518621));
builder.addPoint(new Point(-6.0875711, 54.518622));
Feature addedFeature = polygonTable.createFeature(attributes, builder.toGeometry());
// add feature to collection table
polygonTable.addFeatureAsync(addedFeature);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Location permission was granted. This would have been triggered in response to failing to start the
// LocationDisplay, so try starting this again.
mLocationDisplay.startAsync();
} else {
// If permission was denied, show toast to inform user what was chosen. If LocationDisplay is started again,
// request permission UX will be shown again, option should be shown to allow never showing the UX again.
// Alternative would be to disable functionality so request is not shown again.
Toast.makeText(this, getString(R.string.location_permission_denied), Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onPause() {
super.onPause();
mMapView.pause();
}
@Override
protected void onResume() {
super.onResume();
mMapView.resume();
}
@Override
protected void onDestroy() {
super.onDestroy();
mMapView.dispose();
}
private GeotriggerMonitor createGeotriggerMonitor(FeatureCollectionTable featureTable,
Double bufferDistance,
String geotriggerName,
LocationGeotriggerFeed geotriggerFeed) {
// Initialize FeatureFenceParameters with the polygon feature table and a buffer of 5 meters
FeatureFenceParameters featureFenceParameters = new FeatureFenceParameters(featureTable, bufferDistance);
FenceGeotrigger fenceGeotrigger = new FenceGeotrigger(geotriggerFeed,
FenceRuleType.ENTER_OR_EXIT,
featureFenceParameters,
new ArcadeExpression("$fenceFeature.name"),
geotriggerName);
// Handles Geotrigger notification based on the FenceRuleType
// Hence, triggers on fence enter/exit.
GeotriggerMonitor geotriggerMonitor = new GeotriggerMonitor(fenceGeotrigger);
geotriggerMonitor.addGeotriggerMonitorNotificationEventListener(new GeotriggerMonitorNotificationEventListener() {
@Override
public void onGeotriggerMonitorNotification(GeotriggerMonitorNotificationEvent geotriggerMonitorNotificationEvent) {
Log.d(MAIN_ACTIVITY, "Received geo trigger notification");
handleGeotriggerNotification(geotriggerMonitorNotificationEvent.getGeotriggerNotificationInfo());
}
});
// Start must be explicitly called. It is called after the signal connection is defined to avoid a race condition.
geotriggerMonitor.startAsync();
return geotriggerMonitor;
}
/**
* Handles the geotrigger notification based on [geotriggerNotificationInfo] depending
* on the fenceNotificationType
*/
private void handleGeotriggerNotification(GeotriggerNotificationInfo geotriggerNotificationInfo) {
// FenceGeotriggerNotificationInfo provides access to the feature that triggered the notification
FenceGeotriggerNotificationInfo fenceGeotriggerNotificationInfo =
(FenceGeotriggerNotificationInfo) geotriggerNotificationInfo;
// name of the fence feature
String fenceFeatureName = fenceGeotriggerNotificationInfo.getMessage();
if (fenceGeotriggerNotificationInfo.getFenceNotificationType() == FenceNotificationType.ENTERED) {
// If the user enters a given geofence, add the feature's information to the UI and save the feature for querying
Log.d(MAIN_ACTIVITY, "-----------------> Entered");
} else if (fenceGeotriggerNotificationInfo.getFenceNotificationType() == FenceNotificationType.EXITED) {
// If the user exits a given geofence, remove the feature's information from the UI
Log.d(MAIN_ACTIVITY, "Exited ----------------->");
}
sendNotification(fenceGeotriggerNotificationInfo);
}
private void sendNotification(FenceGeotriggerNotificationInfo fenceGeotriggerNotificationInfo){
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
FenceNotificationType notificationType = fenceGeotriggerNotificationInfo.getFenceNotificationType();
NotificationCompat.Builder notificationBuilder = null;
int notificationId = 0;
if(notificationType == FenceNotificationType.ENTERED){
notificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_entering_geo_fence)
.setContentTitle("Entering " + fenceGeotriggerNotificationInfo.getFenceGeoElement().getAttributes().get("Area"))
.setContentText(fenceGeotriggerNotificationInfo.getMessage())
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(fenceGeotriggerNotificationInfo.getMessage()))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true);
notificationId = 0;
} else {
notificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_leaving_geo_fence)
.setContentTitle("Leaving " + fenceGeotriggerNotificationInfo.getFenceGeoElement().getAttributes().get("Area"))
.setContentText(fenceGeotriggerNotificationInfo.getMessage())
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(fenceGeotriggerNotificationInfo.getMessage()))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true);
notificationId = 1;
}
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
// notificationId is a unique int for each notification that you must define
notificationManager.notify(notificationId, notificationBuilder.build());
}
}
... View more
06-29-2022
04:57 AM
|
0
|
4
|
609
|
Online Status |
Offline
|
Date Last Visited |
08-15-2022
11:33 AM
|