package mil.nga.giat.mage.map; import android.Manifest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.location.Geocoder; import android.location.Location; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.text.SpannableString; import android.text.util.Linkify; import android.util.Log; import android.util.Pair; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.webkit.WebView; import android.widget.ImageButton; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.SearchView; import androidx.core.content.ContextCompat; import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener; import com.google.android.gms.maps.GoogleMap.OnMapClickListener; import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener; import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener; import com.google.android.gms.maps.LocationSource; import com.google.android.gms.maps.MapView; import com.google.android.gms.maps.MapsInitializer; import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.LatLngBounds; import com.google.android.gms.maps.model.MapStyleOptions; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.TileOverlay; import com.google.android.gms.maps.model.TileOverlayOptions; import com.google.android.gms.maps.model.TileProvider; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import javax.inject.Inject; import dagger.android.support.DaggerFragment; import mil.nga.geopackage.BoundingBox; import mil.nga.geopackage.GeoPackage; import mil.nga.geopackage.GeoPackageCache; import mil.nga.geopackage.GeoPackageManager; import mil.nga.geopackage.extension.link.FeatureTileTableLinker; import mil.nga.geopackage.extension.scale.TileScaling; import mil.nga.geopackage.extension.scale.TileTableScaling; import mil.nga.geopackage.factory.GeoPackageFactory; import mil.nga.geopackage.features.user.FeatureCursor; import mil.nga.geopackage.features.user.FeatureDao; import mil.nga.geopackage.features.user.FeatureRow; import mil.nga.geopackage.geom.GeoPackageGeometryData; import mil.nga.geopackage.map.geom.GoogleMapShape; import mil.nga.geopackage.map.geom.GoogleMapShapeConverter; import mil.nga.geopackage.map.tiles.overlay.BoundedOverlay; import mil.nga.geopackage.map.tiles.overlay.FeatureOverlay; import mil.nga.geopackage.map.tiles.overlay.FeatureOverlayQuery; import mil.nga.geopackage.map.tiles.overlay.GeoPackageOverlayFactory; import mil.nga.geopackage.tiles.TileBoundingBoxUtils; import mil.nga.geopackage.tiles.features.DefaultFeatureTiles; import mil.nga.geopackage.tiles.features.FeatureTiles; import mil.nga.geopackage.tiles.features.custom.NumberFeaturesTile; import mil.nga.geopackage.tiles.user.TileDao; import mil.nga.giat.mage.R; import mil.nga.giat.mage.filter.DateTimeFilter; import mil.nga.giat.mage.filter.Filter; import mil.nga.giat.mage.filter.FilterActivity; import mil.nga.giat.mage.location.LocationPolicy; import mil.nga.giat.mage.map.cache.CacheOverlay; import mil.nga.giat.mage.map.cache.CacheOverlayFilter; import mil.nga.giat.mage.map.cache.CacheOverlayType; import mil.nga.giat.mage.map.cache.CacheProvider; import mil.nga.giat.mage.map.cache.CacheProvider.OnCacheOverlayListener; import mil.nga.giat.mage.map.cache.GeoPackageCacheOverlay; import mil.nga.giat.mage.map.cache.GeoPackageFeatureTableCacheOverlay; import mil.nga.giat.mage.map.cache.GeoPackageTileTableCacheOverlay; import mil.nga.giat.mage.map.cache.StaticFeatureCacheOverlay; import mil.nga.giat.mage.map.cache.URLCacheOverlay; import mil.nga.giat.mage.map.cache.WMSCacheOverlay; import mil.nga.giat.mage.map.cache.XYZDirectoryCacheOverlay; import mil.nga.giat.mage.map.marker.LocationMarkerCollection; import mil.nga.giat.mage.map.marker.MyHistoricalLocationMarkerCollection; import mil.nga.giat.mage.map.marker.ObservationMarkerCollection; import mil.nga.giat.mage.map.marker.PointCollection; import mil.nga.giat.mage.map.marker.StaticGeometryCollection; import mil.nga.giat.mage.map.preference.MapPreferencesActivity; import mil.nga.giat.mage.observation.ObservationEditActivity; import mil.nga.giat.mage.observation.ObservationFormPickerActivity; import mil.nga.giat.mage.observation.ObservationLocation; import mil.nga.giat.mage.observation.ObservationViewActivity; import mil.nga.giat.mage.profile.ProfileActivity; import mil.nga.giat.mage.sdk.Temporal; import mil.nga.giat.mage.sdk.datastore.layer.Layer; import mil.nga.giat.mage.sdk.datastore.layer.LayerHelper; import mil.nga.giat.mage.sdk.datastore.location.LocationHelper; import mil.nga.giat.mage.sdk.datastore.location.LocationProperty; import mil.nga.giat.mage.sdk.datastore.observation.Observation; import mil.nga.giat.mage.sdk.datastore.observation.ObservationHelper; import mil.nga.giat.mage.sdk.datastore.user.Event; import mil.nga.giat.mage.sdk.datastore.user.EventHelper; import mil.nga.giat.mage.sdk.datastore.user.User; import mil.nga.giat.mage.sdk.datastore.user.UserHelper; import mil.nga.giat.mage.sdk.event.ILocationEventListener; import mil.nga.giat.mage.sdk.event.IObservationEventListener; import mil.nga.giat.mage.sdk.event.IUserEventListener; import mil.nga.giat.mage.sdk.exceptions.LayerException; import mil.nga.giat.mage.sdk.exceptions.UserException; import mil.nga.mgrs.MGRS; import mil.nga.mgrs.gzd.MGRSTileProvider; import mil.nga.sf.Geometry; import mil.nga.sf.GeometryType; import mil.nga.sf.proj.Projection; import mil.nga.sf.proj.ProjectionConstants; import mil.nga.sf.proj.ProjectionFactory; public class MapFragment extends DaggerFragment implements OnMapReadyCallback, OnMapClickListener, OnMapLongClickListener, OnMarkerClickListener, OnInfoWindowClickListener, GoogleMap.OnCameraMoveListener, GoogleMap.OnCameraIdleListener, GoogleMap.OnCameraMoveStartedListener, OnClickListener, LocationSource, OnCacheOverlayListener, IObservationEventListener, ILocationEventListener, IUserEventListener, Observer<Location> { private static final String LOG_NAME = MapFragment.class.getName(); private static final String MAP_VIEW_STATE = "MAP_VIEW_STATE"; private static final String OBSERVATION_FILTER_TYPE = "Observation"; private static final String LOCATION_FILTER_TYPE = "Location"; private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1; private static final int MARKER_REFRESH_INTERVAL_SECONDS = 300; private static final int OBSERVATION_REFRESH_INTERVAL_SECONDS = 60; private enum LocateState { OFF, FOLLOW; public LocateState next() { return this.ordinal() < LocateState.values().length - 1 ? LocateState.values()[this.ordinal() + 1] : LocateState.OFF; } } @Inject protected Context context; @Inject protected SharedPreferences preferences; private MapView mapView; private GoogleMap map; private View searchLayout; private SearchView searchView; private LocateState locateState = LocateState.OFF; protected User currentUser = null; private long currentEventId = -1; private OnLocationChangedListener locationChangedListener; @Inject protected LocationPolicy locationPolicy; private LiveData<Location> locationProvider; private RefreshMarkersRunnable refreshObservationsTask; private RefreshMarkersRunnable refreshLocationsTask; private RefreshMarkersRunnable refreshHistoricLocationsTask; private PointCollection<Observation> observations; private PointCollection<Pair<mil.nga.giat.mage.sdk.datastore.location.Location, User>> locations; private PointCollection<Pair<mil.nga.giat.mage.sdk.datastore.location.Location, User>> historicLocations; private StaticGeometryCollection staticGeometryCollection; private List<Marker> searchMarkers = new ArrayList<>(); private Map<String, CacheOverlay> cacheOverlays = new HashMap<>(); // GeoPackage cache of open GeoPackage connections private GeoPackageCache geoPackageCache; private BoundingBox addedCacheBoundingBox; private FloatingActionButton compassButton; private FloatingActionButton searchButton; private FloatingActionButton zoomToLocationButton; private boolean showMgrs; private TileOverlay mgrsTileOverlay; private BottomSheetBehavior mgrsBottomSheetBehavior; private View availableLayerDownloadsIcon; private View mgrsBottomSheet; private View mgrsCursor; private TextView mgrsTextView; private TextView mgrsGzdTextView; private TextView mgrs100dKmTextView; private TextView mgrsEastingTextView; private TextView mgrsNorthingTextView; @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_map, container, false); setHasOptionsMenu(true); staticGeometryCollection = new StaticGeometryCollection(); availableLayerDownloadsIcon = view.findViewById(R.id.available_layer_downloads); zoomToLocationButton = view.findViewById(R.id.zoom_button); compassButton = view.findViewById(R.id.compass_button); compassButton.setOnClickListener(v -> resetMapBearing()); searchButton = view.findViewById(R.id.map_search_button); if (Geocoder.isPresent()) { searchButton.setOnClickListener(v -> search()); } else { searchButton.hide(); } view.findViewById(R.id.new_observation_button).setOnClickListener(v -> onNewObservation()); searchLayout = view.findViewById(R.id.search_layout); searchView = view.findViewById(R.id.search_view); searchView.setIconifiedByDefault(false); searchView.setIconified(false); searchView.clearFocus(); MapsInitializer.initialize(context); ImageButton mapSettings = view.findViewById(R.id.map_settings); mapSettings.setOnClickListener(this); mapView = view.findViewById(R.id.mapView); Bundle mapState = (savedInstanceState != null) ? savedInstanceState.getBundle(MAP_VIEW_STATE): null; mapView.onCreate(mapState); mgrsBottomSheet = view.findViewById(R.id.mgrs_bottom_sheet); mgrsBottomSheetBehavior = BottomSheetBehavior.from(mgrsBottomSheet); mgrsCursor = view.findViewById(R.id.mgrs_grid_cursor); mgrsTextView = mgrsBottomSheet.findViewById(R.id.mgrs_code); mgrsGzdTextView = mgrsBottomSheet.findViewById(R.id.mgrs_gzd_zone); mgrs100dKmTextView = mgrsBottomSheet.findViewById(R.id.mgrs_grid_zone); mgrsEastingTextView = mgrsBottomSheet.findViewById(R.id.mgrs_easting); mgrsNorthingTextView = mgrsBottomSheet.findViewById(R.id.mgrs_northing); // Initialize the GeoPackage cache with a GeoPackage manager GeoPackageManager geoPackageManager = GeoPackageFactory.getManager(getActivity().getApplicationContext()); geoPackageCache = new GeoPackageCache(geoPackageManager); locationProvider = locationPolicy.getBestLocationProvider(); return view; } @Override public void onChanged(@Nullable Location location) { if (locationChangedListener != null) { locationChangedListener.onLocationChanged(location); } if (locateState == LocateState.FOLLOW) { CameraPosition cameraPosition = new CameraPosition.Builder() .target(new LatLng(location.getLatitude(), location.getLongitude())) .zoom(17) .bearing(location.getBearing()) .build(); map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)); } } @Override public void onDestroyView() { super.onDestroyView(); mapView.onDestroy(); if (observations != null) { observations.clear(); observations = null; } if (locations != null) { locations.clear(); locations = null; } if (historicLocations != null) { historicLocations.clear(); historicLocations = null; } if (searchMarkers != null) { for (Marker m : searchMarkers) { m.remove(); } searchMarkers.clear(); } if (mgrsTileOverlay != null) { mgrsTileOverlay.remove(); mgrsTileOverlay = null; } // Close all open GeoPackages geoPackageCache.closeAll(); cacheOverlays.clear(); staticGeometryCollection = null; currentUser = null; map = null; } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.filter, menu); getFilterTitle(); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.filter_button: Intent intent = new Intent(getActivity(), FilterActivity.class); startActivity(intent); return true; default: return super.onOptionsItemSelected(item); } } @Override public void onMapReady(GoogleMap googleMap) { if (map == null) { map = googleMap; map.getUiSettings().setMyLocationButtonEnabled(false); map.setOnMapClickListener(this); map.setOnMarkerClickListener(this); map.setOnMapLongClickListener(this); map.setOnInfoWindowClickListener(this); map.setOnCameraIdleListener(this); map.setOnCameraMoveStartedListener(this); map.setOnCameraMoveListener(this); observations = new ObservationMarkerCollection(getActivity(), map); historicLocations = new MyHistoricalLocationMarkerCollection(context, map); locations = new LocationMarkerCollection(getActivity(), map); } int dayNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; if (dayNightMode == Configuration.UI_MODE_NIGHT_NO) { googleMap.setMapStyle(null); } else { googleMap.setMapStyle(MapStyleOptions.loadRawResourceStyle(getContext(), R.raw.map_theme_night)); } boolean showTraffic = preferences.getBoolean(getResources().getString(R.string.showTrafficKey), getResources().getBoolean(R.bool.showTrafficDefaultValue)); map.setTrafficEnabled(showTraffic); Event currentEvent = EventHelper.getInstance(getActivity()).getCurrentEvent(); long currentEventId = this.currentEventId; if (currentEvent != null) { currentEventId = currentEvent.getId(); } if (this.currentEventId != currentEventId) { this.currentEventId = currentEventId; observations.clear(); locations.clear(); historicLocations.clear(); } ObservationHelper.getInstance(context).addListener(this); LocationHelper.getInstance(context).addListener(this); UserHelper.getInstance(context).addListener(this); CacheProvider.getInstance(context).registerCacheOverlayListener(this); zoomToLocationButton.setOnClickListener(v -> { locateState = locateState.next(); switch (locateState) { case OFF: zoomToLocationButton.setSelected(false); resetMapBearing(); break; case FOLLOW: zoomToLocationButton.setSelected(true); Location location = locationProvider.getValue(); if (location != null) { CameraPosition cameraPosition = new CameraPosition.Builder() .target(new LatLng(location.getLatitude(), location.getLongitude())) .zoom(17) .bearing(45) .build(); map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)); } break; } }); ObservationLoadTask observationLoad = new ObservationLoadTask(context, observations); observationLoad.addFilter(getTemporalFilter("timestamp", getTimeFilterId(), OBSERVATION_FILTER_TYPE)); observationLoad.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); HistoricLocationLoadTask myHistoricLocationLoad = new HistoricLocationLoadTask(context, historicLocations); myHistoricLocationLoad.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); LocationLoadTask locationLoad = new LocationLoadTask(context, locations); locationLoad.setFilter(getTemporalFilter("timestamp", getLocationTimeFilterId(), LOCATION_FILTER_TYPE)); locationLoad.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); updateMapView(); // Set visibility on map markers as preferences may have changed observations.setVisibility(preferences.getBoolean(getResources().getString(R.string.showObservationsKey), true)); locations.setVisibility(preferences.getBoolean(getResources().getString(R.string.showLocationsKey), true)); historicLocations.setVisibility(preferences.getBoolean(getResources().getString(R.string.showMyLocationHistoryKey), false)); // Check if any map preferences changed that I care about if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { map.setMyLocationEnabled(true); map.setLocationSource(this); } else { map.setMyLocationEnabled(false); map.setLocationSource(null); } initializePeriodicTasks(); mgrsCursor.setVisibility(showMgrs ? View.VISIBLE : View.GONE); if (showMgrs) { mgrsTileOverlay = map.addTileOverlay(new TileOverlayOptions().tileProvider(new MGRSTileProvider(getContext()))); } ((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(getFilterTitle()); } private void resetMapBearing() { if (map == null) return; CameraPosition cameraPosition = new CameraPosition.Builder(map.getCameraPosition()) .bearing(0) .build(); map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)); } private void initializePeriodicTasks() { if (refreshObservationsTask == null) { refreshObservationsTask = new RefreshMarkersRunnable(observations, "timestamp", OBSERVATION_FILTER_TYPE, getTimeFilterId(), OBSERVATION_REFRESH_INTERVAL_SECONDS); scheduleMarkerRefresh(refreshObservationsTask); } if (refreshLocationsTask == null) { refreshLocationsTask = new RefreshMarkersRunnable(locations, "timestamp", LOCATION_FILTER_TYPE, getLocationTimeFilterId(), MARKER_REFRESH_INTERVAL_SECONDS); scheduleMarkerRefresh(refreshLocationsTask); } if (refreshHistoricLocationsTask == null) { refreshHistoricLocationsTask = new RefreshMarkersRunnable(historicLocations, "timestamp", LOCATION_FILTER_TYPE, getLocationTimeFilterId(), MARKER_REFRESH_INTERVAL_SECONDS); scheduleMarkerRefresh(refreshHistoricLocationsTask); } } private void stopPeriodicTasks() { getView().removeCallbacks(refreshObservationsTask); getView().removeCallbacks(refreshLocationsTask); getView().removeCallbacks(refreshHistoricLocationsTask); refreshObservationsTask = null; refreshLocationsTask = null; refreshHistoricLocationsTask = null; } @Override public void onLowMemory() { super.onLowMemory(); mapView.onLowMemory(); } @Override public void onResume() { super.onResume(); try { currentUser = UserHelper.getInstance(getActivity().getApplicationContext()).readCurrentUser(); } catch (UserException ue) { Log.e(LOG_NAME, "Could not find current user.", ue); } mapView.onResume(); // Don't wait for map to show up to init these values, otherwise bottomsheet will jitter showMgrs = preferences.getBoolean(getResources().getString(R.string.showMGRSKey), false); mgrsBottomSheetBehavior.setHideable(!showMgrs); mgrsBottomSheetBehavior.setState(showMgrs ? BottomSheetBehavior.STATE_COLLAPSED : BottomSheetBehavior.STATE_HIDDEN); if (map == null) { mapView.getMapAsync(this); } else { onMapReady(map); } try { Event event = EventHelper.getInstance(getContext()).getCurrentEvent(); Collection<Layer> available = Collections2.filter(LayerHelper.getInstance(getContext()).readByEvent(event, "GeoPackage"), new Predicate<Layer>() { @Override public boolean apply(Layer layer) { return !layer.isLoaded(); } }); availableLayerDownloadsIcon.setVisibility(available.isEmpty() ? View.GONE : View.VISIBLE); } catch (LayerException e) { Log.e(LOG_NAME, "Error reading layers", e); } searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { if (StringUtils.isNoneBlank(query)) { new GeocoderTask(getActivity(), map, searchMarkers).execute(query); } searchView.clearFocus(); return true; } @Override public boolean onQueryTextChange(String newText) { if (StringUtils.isEmpty(newText)) { if (searchMarkers != null) { for (Marker m : searchMarkers) { m.remove(); } searchMarkers.clear(); } } return true; } }); } @Override public void onPause() { super.onPause(); stopPeriodicTasks(); mapView.onPause(); ObservationHelper.getInstance(context).removeListener(this); LocationHelper.getInstance(context).removeListener(this); UserHelper.getInstance(context).removeListener(this); if (observations != null) { observations.clear(); } if (locations != null) { locations.clear(); } CacheProvider.getInstance(getActivity().getApplicationContext()).unregisterCacheOverlayListener(this); if (map != null) { saveMapView(); map.setLocationSource(null); if (mgrsTileOverlay != null) { mgrsTileOverlay.remove(); } } } private void search() { boolean isVisible = searchLayout.getVisibility() == View.VISIBLE; searchLayout.setVisibility(isVisible ? View.GONE : View.VISIBLE); searchButton.setSelected(!isVisible); if (isVisible) { searchView.clearFocus(); } else { searchView.requestFocus(); InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE); inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0); } } private void onNewObservation() { ObservationLocation location = null; // if there is not a location from the location service, then try to pull one from the database. if (locationProvider.getValue() == null) { List<mil.nga.giat.mage.sdk.datastore.location.Location> locations = LocationHelper.getInstance(getActivity().getApplicationContext()).getCurrentUserLocations(1, true); if (!locations.isEmpty()) { mil.nga.giat.mage.sdk.datastore.location.Location tLocation = locations.get(0); Geometry geo = tLocation.getGeometry(); Map<String, LocationProperty> propertiesMap = tLocation.getPropertiesMap(); String provider = ObservationLocation.MANUAL_PROVIDER; if (propertiesMap.get("provider").getValue() != null) { provider = propertiesMap.get("provider").getValue().toString(); } location = new ObservationLocation(provider, geo); location.setTime(tLocation.getTimestamp().getTime()); if (propertiesMap.get("accuracy").getValue() != null) { location.setAccuracy(Float.valueOf(propertiesMap.get("accuracy").getValue().toString())); } } } else { location = new ObservationLocation(locationProvider.getValue()); } if (!UserHelper.getInstance(getActivity().getApplicationContext()).isCurrentUserPartOfCurrentEvent()) { new AlertDialog.Builder(getActivity()) .setTitle(getActivity().getResources().getString(R.string.location_no_event_title)) .setMessage(getActivity().getResources().getString(R.string.location_no_event_message)) .setPositiveButton(android.R.string.ok, null) .show(); } else if (location != null) { newObservation(location); } else { if (ContextCompat.checkSelfPermission(getActivity().getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { new AlertDialog.Builder(getActivity()) .setTitle(getActivity().getResources().getString(R.string.location_missing_title)) .setMessage(getActivity().getResources().getString(R.string.location_missing_message)) .setPositiveButton(android.R.string.ok, null) .show(); } else { new AlertDialog.Builder(getActivity()) .setTitle(getActivity().getResources().getString(R.string.location_access_observation_title)) .setMessage(getActivity().getResources().getString(R.string.location_access_observation_message)) .setPositiveButton(android.R.string.ok, new Dialog.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION); } }) .show(); } } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { switch (requestCode) { case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { onNewObservation(); } break; } } } @Override public void onObservationCreated(Collection<Observation> o, Boolean sendUserNotifcations) { if (observations != null) { ObservationTask task = new ObservationTask(getActivity(), ObservationTask.Type.ADD, observations); task.addFilter(getTemporalFilter("last_modified", getTimeFilterId(), OBSERVATION_FILTER_TYPE)); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, o.toArray(new Observation[o.size()])); } } @Override public void onObservationUpdated(Observation o) { if (observations != null) { ObservationTask task = new ObservationTask(getActivity(), ObservationTask.Type.UPDATE, observations); task.addFilter(getTemporalFilter("last_modified", getTimeFilterId(), OBSERVATION_FILTER_TYPE)); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, o); } } @Override public void onObservationDeleted(Observation o) { if (observations != null) { new ObservationTask(context, ObservationTask.Type.DELETE, observations).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, o); } } @Override public void onLocationCreated(Collection<mil.nga.giat.mage.sdk.datastore.location.Location> ls) { for (mil.nga.giat.mage.sdk.datastore.location.Location l : ls) { if (currentUser != null && !currentUser.getRemoteId().equals(l.getUser().getRemoteId())) { if (locations != null) { LocationTask task = new LocationTask(getActivity(), LocationTask.Type.ADD, locations); task.addFilter(getTemporalFilter("timestamp", getLocationTimeFilterId(), LOCATION_FILTER_TYPE)); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, l); } } else { if (historicLocations != null) { new LocationTask(context, LocationTask.Type.ADD, historicLocations).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, l); } } } } @Override public void onLocationUpdated(mil.nga.giat.mage.sdk.datastore.location.Location l) { if (currentUser != null && !currentUser.getRemoteId().equals(l.getUser().getRemoteId())) { if (locations != null) { LocationTask task = new LocationTask(getActivity(), LocationTask.Type.UPDATE, locations); task.addFilter(getTemporalFilter("timestamp", getLocationTimeFilterId(), LOCATION_FILTER_TYPE)); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, l); } } else { if (historicLocations != null) { new LocationTask(context, LocationTask.Type.UPDATE, historicLocations).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, l); } } } @Override public void onLocationDeleted(Collection<mil.nga.giat.mage.sdk.datastore.location.Location> l) { // this is slowing the app down a lot! Moving the delete like code into the add methods of the collections /* if (currentUser != null && !currentUser.getRemoteId().equals(l.getUser().getRemoteId())) { if (locations != null) { new LocationTask(LocationTask.Type.DELETE, locations).execute(l); } } else { if (myHistoricLocations != null) { new LocationTask(LocationTask.Type.DELETE, myHistoricLocations).execute(l); } } */ } @Override public void onUserCreated(User user) {} @Override public void onUserUpdated(User user) {} @Override public void onUserIconUpdated(final User user) { new Handler(Looper.getMainLooper()).post(() -> { if (locations == null) { return; } locations.refresh(new Pair(new mil.nga.giat.mage.sdk.datastore.location.Location(), user)); }); } @Override public void onUserAvatarUpdated(User user) { } @Override public void onInfoWindowClick(Marker marker) { Observation observation = observations.pointForMarker(marker); if (observation != null) { Intent intent = new Intent(context, ObservationViewActivity.class); intent.putExtra(ObservationViewActivity.OBSERVATION_ID, observation.getId()); intent.putExtra(ObservationViewActivity.INITIAL_LOCATION, map.getCameraPosition().target); intent.putExtra(ObservationViewActivity.INITIAL_ZOOM, map.getCameraPosition().zoom); startActivity(intent); return; } Pair<mil.nga.giat.mage.sdk.datastore.location.Location, User> pair = locations.pointForMarker(marker); if (pair != null) { Intent profileView = new Intent(context, ProfileActivity.class); profileView.putExtra(ProfileActivity.USER_ID, pair.second.getRemoteId()); startActivity(profileView); return; } } @Override public boolean onMarkerClick(Marker marker) { hideKeyboard(); observations.offMarkerClick(); // search marker if(searchMarkers != null) { for(Marker m :searchMarkers) { if(marker.getId().equals(m.getId())) { m.showInfoWindow(); return true; } } } // You can only have one marker click listener per map. // Lets listen here and shell out the click event to all // my marker collections. Each one need to handle // gracefully if it does not actually contain the marker if (observations.onMarkerClick(marker)) { return true; } if (locations.onMarkerClick(marker)) { return true; } if (historicLocations.onMarkerClick(marker)) { return true; } // static layer if(marker.getSnippet() != null) { View markerInfoWindow = LayoutInflater.from(getActivity()).inflate(R.layout.static_feature_infowindow, null, false); WebView webView = markerInfoWindow.findViewById(R.id.static_feature_infowindow_content); webView.loadData(marker.getSnippet(), "text/html; charset=UTF-8", null); new AlertDialog.Builder(getActivity()) .setView(markerInfoWindow) .setPositiveButton(android.R.string.yes, null) .show(); } return true; } @Override public void onMapClick(LatLng latLng) { hideKeyboard(); // remove old accuracy circle locations.offMarkerClick(); observations.offMarkerClick(); observations.onMapClick(latLng); staticGeometryCollection.onMapClick(map, latLng, getActivity()); if (!cacheOverlays.isEmpty()) { StringBuilder clickMessage = new StringBuilder(); for (CacheOverlay cacheOverlay : cacheOverlays.values()) { String message = cacheOverlay.onMapClick(latLng, mapView, map); if (message != null) { if (clickMessage.length() > 0) { clickMessage.append("\n\n"); } clickMessage.append(message); } } if (clickMessage.length() > 0) { final SpannableString text = new SpannableString(clickMessage.toString()); Linkify.addLinks(text, Linkify.WEB_URLS); View view = getLayoutInflater().inflate(R.layout.view_feature_details, null); TextView textView = view.findViewById(R.id.text); textView.setText(text); new AlertDialog.Builder(getActivity()) .setView(view) .setPositiveButton(android.R.string.yes, null) .show(); } } } @Override public void onMapLongClick(LatLng point) { hideKeyboard(); if(!UserHelper.getInstance(getActivity().getApplicationContext()).isCurrentUserPartOfCurrentEvent()) { new AlertDialog.Builder(getActivity()) .setTitle(getActivity().getResources().getString(R.string.location_no_event_title)) .setMessage(getActivity().getResources().getString(R.string.location_no_event_message)) .setPositiveButton(android.R.string.ok, null) .show(); } else { ObservationLocation location = new ObservationLocation(ObservationLocation.MANUAL_PROVIDER, point); location.setAccuracy(0.0f); location.setTime(new Date().getTime()); newObservation(location); } } private void newObservation(ObservationLocation location) { Intent intent; // show form picker or go to JsonArray formDefinitions = EventHelper.getInstance(getActivity()).getCurrentEvent().getNonArchivedForms(); if (formDefinitions.size() == 0) { intent = new Intent(getActivity(), ObservationEditActivity.class); } else if (formDefinitions.size() == 1) { JsonObject form = (JsonObject) formDefinitions.iterator().next(); intent = new Intent(getActivity(), ObservationEditActivity.class); intent.putExtra(ObservationEditActivity.OBSERVATION_FORM_ID, form.get("id").getAsLong()); } else { intent = new Intent(getActivity(), ObservationFormPickerActivity.class); } intent.putExtra(ObservationEditActivity.LOCATION, location); intent.putExtra(ObservationEditActivity.INITIAL_LOCATION, map.getCameraPosition().target); intent.putExtra(ObservationEditActivity.INITIAL_ZOOM, map.getCameraPosition().zoom); startActivity(intent); } @Override public void onClick(View view) { // close keyboard hideKeyboard(); switch (view.getId()) { case R.id.map_settings: { Intent intent = new Intent(getActivity(), MapPreferencesActivity.class); startActivity(intent); break; } } } @Override public void activate(OnLocationChangedListener listener) { Log.i(LOG_NAME, "map location, activate"); locationChangedListener = listener; Location location = locationProvider.getValue(); if (location != null) { Log.i(LOG_NAME, "map location, activate we have a location, let our listener know"); locationChangedListener.onLocationChanged(location); } locationProvider.observe(this, this); } @Override public void deactivate() { Log.i(LOG_NAME, "map location, deactivate"); locationProvider.removeObserver(this); locationChangedListener = null; } @Override public void onCameraIdle() { setMgrsCode(); } @Override public void onCameraMoveStarted(int reason) { if (reason == REASON_GESTURE) { locateState = LocateState.OFF; zoomToLocationButton.setSelected(false); resetMapBearing(); } } @Override public void onCameraMove() { float bearing = map.getCameraPosition().bearing; if (bearing != 0) { compassButton.animate().alpha(1f).setDuration(0).setListener(null); compassButton.show(); compassButton.setRotation(bearing); } else { compassButton .animate() .alpha(0f) .setDuration(500) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { compassButton.hide(); } }); } } private void setMgrsCode() { if (mgrsTileOverlay != null) { LatLng center = map.getCameraPosition().target; MGRS mgrs = MGRS.from(new mil.nga.mgrs.wgs84.LatLng(center.latitude, center.longitude)); mgrsTextView.setText(mgrs.format(5)); mgrsGzdTextView.setText(String.format(Locale.getDefault(),"%s%c", mgrs.getZone(), mgrs.getBand())); mgrs100dKmTextView.setText(String.format(Locale.getDefault(),"%c%c", mgrs.getE100k(), mgrs.getN100k())); mgrsEastingTextView.setText(String.format(Locale.getDefault(),"%05d", mgrs.getEasting())); mgrsNorthingTextView.setText(String.format(Locale.getDefault(),"%05d", mgrs.getNorthing())); } } @Override public void onCacheOverlay(List<CacheOverlay> cacheOverlays) { // Add all overlays that are in the preferences Event currentEvent = EventHelper.getInstance(getActivity()).getCurrentEvent(); cacheOverlays = new CacheOverlayFilter(getContext(), currentEvent).filter(cacheOverlays); // Track enabled cache overlays Map<String, CacheOverlay> enabledCacheOverlays = new HashMap<>(); // Track enabled GeoPackages Set<String> enabledGeoPackages = new HashSet<>(); // Reset the bounding box for newly added caches addedCacheBoundingBox = null; for (CacheOverlay cacheOverlay : cacheOverlays) { if(cacheOverlay instanceof StaticFeatureCacheOverlay){ staticGeometryCollection.removeLayer(((StaticFeatureCacheOverlay)cacheOverlay).getId().toString()); } // If this cache overlay potentially replaced by a new version if(cacheOverlay.isAdded()){ if(cacheOverlay.getType() == CacheOverlayType.GEOPACKAGE){ geoPackageCache.close(cacheOverlay.getName()); } } // The user has asked for this overlay if (cacheOverlay.isEnabled()) { // Handle each type of cache overlay switch(cacheOverlay.getType()) { case XYZ_DIRECTORY: addXYZDirectoryCacheOverlay(enabledCacheOverlays, (XYZDirectoryCacheOverlay) cacheOverlay); break; case GEOPACKAGE: addGeoPackageCacheOverlay(enabledCacheOverlays, enabledGeoPackages, (GeoPackageCacheOverlay)cacheOverlay); break; case URL: addURLCacheOverlay(enabledCacheOverlays, (URLCacheOverlay)cacheOverlay); break; case STATIC_FEATURE: addStaticFeatureOverlay(enabledCacheOverlays, (StaticFeatureCacheOverlay)cacheOverlay); break; } } cacheOverlay.setAdded(false); } // Remove any overlays that are on the map but no longer selected in // preferences, update the tile overlays to the enabled tile overlays for (CacheOverlay cacheOverlay : this.cacheOverlays.values()) { cacheOverlay.removeFromMap(); } this.cacheOverlays = enabledCacheOverlays; // Close GeoPackages no longer enabled geoPackageCache.closeRetain(enabledGeoPackages); // If a new cache was added, zoom to the bounding box area if(addedCacheBoundingBox != null){ final LatLngBounds.Builder boundsBuilder = new LatLngBounds.Builder(); boundsBuilder.include(new LatLng(addedCacheBoundingBox.getMinLatitude(), addedCacheBoundingBox .getMinLongitude())); boundsBuilder.include(new LatLng(addedCacheBoundingBox.getMinLatitude(), addedCacheBoundingBox .getMaxLongitude())); boundsBuilder.include(new LatLng(addedCacheBoundingBox.getMaxLatitude(), addedCacheBoundingBox .getMinLongitude())); boundsBuilder.include(new LatLng(addedCacheBoundingBox.getMaxLatitude(), addedCacheBoundingBox .getMaxLongitude())); try { map.animateCamera(CameraUpdateFactory.newLatLngBounds( boundsBuilder.build(), 0)); } catch (Exception e) { Log.e(LOG_NAME, "Unable to move camera to newly added cache location", e); } } } private void addURLCacheOverlay(Map<String, CacheOverlay> enabledCacheOverlays, URLCacheOverlay urlCacheOverlay){ // Retrieve the cache overlay if it already exists (and remove from cache overlays) CacheOverlay cacheOverlay = cacheOverlays.remove(urlCacheOverlay.getCacheName()); if(cacheOverlay == null){ // Create a new tile provider and add to the map TileProvider tileProvider = null; boolean isTransparent = false; if(urlCacheOverlay.getFormat().equalsIgnoreCase("xyz")) { tileProvider = new XYZTileProvider(256, 256, urlCacheOverlay); }else if(urlCacheOverlay.getFormat().equalsIgnoreCase("tms")){ tileProvider = new TMSTileProvider(256, 256, urlCacheOverlay); }else { tileProvider = new WMSTileProvider(256, 256, urlCacheOverlay); WMSCacheOverlay wms = (WMSCacheOverlay)urlCacheOverlay; isTransparent = Boolean.parseBoolean(wms.getWmsTransparent()); } TileOverlayOptions overlayOptions = createTileOverlayOptions(tileProvider); if(urlCacheOverlay.isBase()) { overlayOptions.zIndex(-4); } else if(!isTransparent) { overlayOptions.zIndex(-3); } else{ overlayOptions.zIndex(-2); } // Set the tile overlay in the cache overlay TileOverlay tileOverlay = map.addTileOverlay(overlayOptions); urlCacheOverlay.setTileOverlay(tileOverlay); cacheOverlay = urlCacheOverlay; } // Add the cache overlay to the enabled cache overlays enabledCacheOverlays.put(cacheOverlay.getCacheName(), cacheOverlay); } private void addStaticFeatureOverlay(Map<String, CacheOverlay> enabledCacheOverlays, final StaticFeatureCacheOverlay cacheOverlay) { try { final Layer layer = LayerHelper.getInstance(getActivity().getApplicationContext()).read(cacheOverlay.getId()); new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { new StaticFeatureLoadTask(getActivity().getApplicationContext(), staticGeometryCollection, map).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, layer); } }); } catch (LayerException e) { Log.e(LOG_NAME, "Problem updating static features.", e); } } /** * Add XYZ Directory tile cache overlay * @param enabledCacheOverlays * @param xyzDirectoryCacheOverlay */ private void addXYZDirectoryCacheOverlay(Map<String, CacheOverlay> enabledCacheOverlays, XYZDirectoryCacheOverlay xyzDirectoryCacheOverlay){ // Retrieve the cache overlay if it already exists (and remove from cache overlays) CacheOverlay cacheOverlay = cacheOverlays.remove(xyzDirectoryCacheOverlay.getCacheName()); if(cacheOverlay == null){ // Create a new tile provider and add to the map TileProvider tileProvider = new FileSystemTileProvider(256, 256, xyzDirectoryCacheOverlay.getDirectory().getAbsolutePath()); TileOverlayOptions overlayOptions = createTileOverlayOptions(tileProvider); // Set the tile overlay in the cache overlay TileOverlay tileOverlay = map.addTileOverlay(overlayOptions); xyzDirectoryCacheOverlay.setTileOverlay(tileOverlay); cacheOverlay = xyzDirectoryCacheOverlay; } // Add the cache overlay to the enabled cache overlays enabledCacheOverlays.put(cacheOverlay.getCacheName(), cacheOverlay); } /** * Add a GeoPackage cache overlay, which contains tile and feature tables * @param enabledCacheOverlays * @param enabledGeoPackages * @param geoPackageCacheOverlay */ private void addGeoPackageCacheOverlay(Map<String, CacheOverlay> enabledCacheOverlays, Set<String> enabledGeoPackages, GeoPackageCacheOverlay geoPackageCacheOverlay){ // Check each GeoPackage table for(CacheOverlay tableCacheOverlay: geoPackageCacheOverlay.getChildren()){ // Check if the table is enabled if(tableCacheOverlay.isEnabled()){ // Get and open if needed the GeoPackage GeoPackage geoPackage = geoPackageCache.getOrOpen(geoPackageCacheOverlay.getName()); enabledGeoPackages.add(geoPackage.getName()); // Handle tile and feature tables try { switch (tableCacheOverlay.getType()) { case GEOPACKAGE_TILE_TABLE: addGeoPackageTileCacheOverlay(enabledCacheOverlays, (GeoPackageTileTableCacheOverlay) tableCacheOverlay, geoPackage, false); break; case GEOPACKAGE_FEATURE_TABLE: addGeoPackageFeatureCacheOverlay(enabledCacheOverlays, (GeoPackageFeatureTableCacheOverlay) tableCacheOverlay, geoPackage); break; default: throw new UnsupportedOperationException("Unsupported GeoPackage type: " + tableCacheOverlay.getType()); } }catch(Exception e){ Log.e(LOG_NAME, "Failed to add GeoPackage overlay. GeoPackage: " + geoPackage.getName() + ", Name: " + tableCacheOverlay.getName(), e); } // If a newly added cache, update the bounding box for zooming if(geoPackageCacheOverlay.isAdded()){ try { BoundingBox boundingBox = geoPackage.getBoundingBox( ProjectionFactory.getProjection(ProjectionConstants.EPSG_WORLD_GEODETIC_SYSTEM), tableCacheOverlay.getName()); if(boundingBox != null) { if (addedCacheBoundingBox == null) { addedCacheBoundingBox = boundingBox; } else { addedCacheBoundingBox = TileBoundingBoxUtils.union(addedCacheBoundingBox, boundingBox); } } }catch(Exception e){ Log.e(LOG_NAME, "Failed to retrieve GeoPackage Table bounding box. GeoPackage: " + geoPackage.getName() + ", Table: " + tableCacheOverlay.getName(), e); } } } } } /** * Add the GeoPackage Tile Table Cache Overlay * @param enabledCacheOverlays * @param tileTableCacheOverlay * @param geoPackage * @param linkedToFeatures */ private void addGeoPackageTileCacheOverlay(Map<String, CacheOverlay> enabledCacheOverlays, GeoPackageTileTableCacheOverlay tileTableCacheOverlay, GeoPackage geoPackage, boolean linkedToFeatures){ // Retrieve the cache overlay if it already exists (and remove from cache overlays) CacheOverlay cacheOverlay = cacheOverlays.remove(tileTableCacheOverlay.getCacheName()); if(cacheOverlay != null){ // If the existing cache overlay is being replaced, create a new cache overlay if(tileTableCacheOverlay.getParent().isAdded()){ cacheOverlay = null; } } if(cacheOverlay == null){ // Create a new GeoPackage tile provider and add to the map TileDao tileDao = geoPackage.getTileDao(tileTableCacheOverlay.getName()); TileTableScaling tileTableScaling = new TileTableScaling(geoPackage, tileDao); TileScaling tileScaling = tileTableScaling.get(); BoundedOverlay overlay = GeoPackageOverlayFactory .getBoundedOverlay(tileDao, getResources().getDisplayMetrics().density, tileScaling); TileOverlayOptions overlayOptions = null; if(linkedToFeatures){ overlayOptions = createFeatureTileOverlayOptions(overlay); }else { overlayOptions = createTileOverlayOptions(overlay); } TileOverlay tileOverlay = map.addTileOverlay(overlayOptions); tileTableCacheOverlay.setTileOverlay(tileOverlay); // Check for linked feature tables tileTableCacheOverlay.clearFeatureOverlayQueries(); FeatureTileTableLinker linker = new FeatureTileTableLinker(geoPackage); List<FeatureDao> featureDaos = linker.getFeatureDaosForTileTable(tileDao.getTableName()); for(FeatureDao featureDao: featureDaos){ // Create the feature tiles FeatureTiles featureTiles = new DefaultFeatureTiles(getActivity(), geoPackage, featureDao, getResources().getDisplayMetrics().density); // Add the feature overlay query FeatureOverlayQuery featureOverlayQuery = new FeatureOverlayQuery(getActivity(), overlay, featureTiles); tileTableCacheOverlay.addFeatureOverlayQuery(featureOverlayQuery); } cacheOverlay = tileTableCacheOverlay; } // Add the cache overlay to the enabled cache overlays enabledCacheOverlays.put(cacheOverlay.getCacheName(), cacheOverlay); } /** * Add the GeoPackage Feature Table Cache Overlay * @param enabledCacheOverlays * @param featureTableCacheOverlay * @param geoPackage */ private void addGeoPackageFeatureCacheOverlay(Map<String, CacheOverlay> enabledCacheOverlays, GeoPackageFeatureTableCacheOverlay featureTableCacheOverlay, GeoPackage geoPackage){ // Retrieve the cache overlay if it already exists (and remove from cache overlays) CacheOverlay cacheOverlay = cacheOverlays.remove(featureTableCacheOverlay.getCacheName()); if(cacheOverlay != null){ // If the existing cache overlay is being replaced, create a new cache overlay if(featureTableCacheOverlay.getParent().isAdded()){ cacheOverlay = null; } for(GeoPackageTileTableCacheOverlay linkedTileTable: featureTableCacheOverlay.getLinkedTileTables()){ cacheOverlays.remove(linkedTileTable.getCacheName()); } } if(cacheOverlay == null) { // Add the features to the map FeatureDao featureDao = geoPackage.getFeatureDao(featureTableCacheOverlay.getName()); // If indexed, add as a tile overlay if(featureTableCacheOverlay.isIndexed()){ FeatureTiles featureTiles = new DefaultFeatureTiles(getActivity(), geoPackage, featureDao, getResources().getDisplayMetrics().density); Integer maxFeaturesPerTile = null; if(featureDao.getGeometryType() == GeometryType.POINT){ maxFeaturesPerTile = getResources().getInteger(R.integer.geopackage_feature_tiles_max_points_per_tile); }else{ maxFeaturesPerTile = getResources().getInteger(R.integer.geopackage_feature_tiles_max_features_per_tile); } featureTiles.setMaxFeaturesPerTile(maxFeaturesPerTile); NumberFeaturesTile numberFeaturesTile = new NumberFeaturesTile(getActivity()); // Adjust the max features number tile draw paint attributes here as needed to // change how tiles are drawn when more than the max features exist in a tile featureTiles.setMaxFeaturesTileDraw(numberFeaturesTile); // Adjust the feature tiles draw paint attributes here as needed to change how // features are drawn on tiles FeatureOverlay featureOverlay = new FeatureOverlay(featureTiles); featureOverlay.setMinZoom(featureTableCacheOverlay.getMinZoom()); // Get the tile linked overlay BoundedOverlay overlay = GeoPackageOverlayFactory.getLinkedFeatureOverlay(featureOverlay, geoPackage); FeatureOverlayQuery featureOverlayQuery = new FeatureOverlayQuery(getActivity(), overlay, featureTiles); featureTableCacheOverlay.setFeatureOverlayQuery(featureOverlayQuery); TileOverlayOptions overlayOptions = createFeatureTileOverlayOptions(overlay); TileOverlay tileOverlay = map.addTileOverlay(overlayOptions); featureTableCacheOverlay.setTileOverlay(tileOverlay); } // Not indexed, add the features to the map else { int maxFeaturesPerTable = 0; if(featureDao.getGeometryType() == GeometryType.POINT){ maxFeaturesPerTable = getResources().getInteger(R.integer.geopackage_features_max_points_per_table); }else{ maxFeaturesPerTable = getResources().getInteger(R.integer.geopackage_features_max_features_per_table); } Projection projection = featureDao.getProjection(); GoogleMapShapeConverter shapeConverter = new GoogleMapShapeConverter(projection); FeatureCursor featureCursor = featureDao.queryForAll(); try { final int totalCount = featureCursor.getCount(); int count = 0; while (featureCursor.moveToNext()) { try { FeatureRow featureRow = featureCursor.getRow(); GeoPackageGeometryData geometryData = featureRow.getGeometry(); if (geometryData != null && !geometryData.isEmpty()) { Geometry geometry = geometryData.getGeometry(); if (geometry != null) { GoogleMapShape shape = shapeConverter.toShape(geometry); // Set the Shape Marker, PolylineOptions, and PolygonOptions here if needed to change color and style featureTableCacheOverlay.addShapeToMap(featureRow.getId(), shape, map); if (++count >= maxFeaturesPerTable) { if (count < totalCount) { Toast.makeText(getActivity().getApplicationContext(), featureTableCacheOverlay.getCacheName() + "- added " + count + " of " + totalCount, Toast.LENGTH_LONG).show(); } break; } } } }catch(Exception e){ Log.e(LOG_NAME, "Failed to display feature. GeoPackage: " + geoPackage.getName() + ", Table: " + featureDao.getTableName() + ", Row: " + featureCursor.getPosition(), e); } } } finally { featureCursor.close(); } } cacheOverlay = featureTableCacheOverlay; } // Add the cache overlay to the enabled cache overlays enabledCacheOverlays.put(cacheOverlay.getCacheName(), cacheOverlay); } /** * Create Feature Tile Overlay Options with the default z index for tile layers drawn from features * @param tileProvider * @return */ private TileOverlayOptions createFeatureTileOverlayOptions(TileProvider tileProvider){ return createTileOverlayOptions(tileProvider, -1); } /** * Create Tile Overlay Options with the default z index for tile layers * @param tileProvider * @return */ private TileOverlayOptions createTileOverlayOptions(TileProvider tileProvider){ return createTileOverlayOptions(tileProvider, -2); } /** * Create Tile Overlay Options for the Tile Provider using the z index * @param tileProvider * @param zIndex * @return */ private TileOverlayOptions createTileOverlayOptions(TileProvider tileProvider, int zIndex){ TileOverlayOptions overlayOptions = new TileOverlayOptions(); overlayOptions.tileProvider(tileProvider); overlayOptions.zIndex(zIndex); return overlayOptions; } private void updateMapView() { // Check the map type map.setMapType(preferences.getInt(getString(R.string.baseLayerKey), getResources().getInteger(R.integer.baseLayerDefaultValue))); // Check the map location and zoom String xyz = preferences.getString(getString(R.string.recentMapXYZKey), getString(R.string.recentMapXYZDefaultValue)); if (xyz != null) { String[] values = StringUtils.split(xyz, ","); LatLng latLng = new LatLng(0.0, 0.0); if(values.length > 1) { try { latLng = new LatLng(Double.valueOf(values[1]), Double.valueOf(values[0])); } catch (NumberFormatException nfe) { Log.e(LOG_NAME, "Could not parse lon,lat: " + String.valueOf(values[1]) + ", " + String.valueOf(values[0])); } } float zoom = 1.0f; if(values.length > 2) { try { zoom = Float.valueOf(values[2]); } catch (NumberFormatException nfe) { Log.e(LOG_NAME, "Could not parse zoom level: " + String.valueOf(values[2])); } } map.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, zoom)); } } private void saveMapView() { CameraPosition position = map.getCameraPosition(); String xyz = new StringBuilder().append(Double.valueOf(position.target.longitude).toString()).append(",").append(Double.valueOf(position.target.latitude).toString()).append(",").append(Float.valueOf(position.zoom).toString()).toString(); preferences.edit().putString(getResources().getString(R.string.recentMapXYZKey), xyz).commit(); } @Override public void onError(Throwable error) { } private int getTimeFilterId() { return preferences.getInt(getResources().getString(R.string.activeTimeFilterKey), getResources().getInteger(R.integer.time_filter_none)); } private int getLocationTimeFilterId() { return preferences.getInt(getResources().getString(R.string.activeLocationTimeFilterKey), getResources().getInteger(R.integer.time_filter_none)); } private int getCustomTimeNumber(String filterType) { if (filterType.equalsIgnoreCase(OBSERVATION_FILTER_TYPE)) { return preferences.getInt(getResources().getString(R.string.customObservationTimeNumberFilterKey), 0); } else { return preferences.getInt(getResources().getString(R.string.customLocationTimeNumberFilterKey), 0); } } private String getCustomTimeUnit(String filterType) { if (filterType.equalsIgnoreCase(OBSERVATION_FILTER_TYPE)) { return preferences.getString(getResources().getString(R.string.customObservationTimeUnitFilterKey), getResources().getStringArray(R.array.timeUnitEntries)[0]); } else { return preferences.getString(getResources().getString(R.string.customLocationTimeUnitFilterKey), getResources().getStringArray(R.array.timeUnitEntries)[0]); } } private Filter<Temporal> getTemporalFilter(String columnName, int filterId, String filterType) { Filter<Temporal> filter = null; Calendar c = Calendar.getInstance(); if (filterId == getResources().getInteger(R.integer.time_filter_last_month)) { c.add(Calendar.MONTH, -1); } else if (filterId == getResources().getInteger(R.integer.time_filter_last_week)) { c.add(Calendar.DAY_OF_MONTH, -7); } else if (filterId == getResources().getInteger(R.integer.time_filter_last_24_hours)) { c.add(Calendar.HOUR, -24); } else if (filterId == getResources().getInteger(R.integer.time_filter_today)) { c.set(Calendar.HOUR_OF_DAY, 0); c.set(Calendar.MINUTE, 0); c.set(Calendar.SECOND, 0); c.set(Calendar.MILLISECOND, 0); } else if (filterId == getResources().getInteger(R.integer.time_filter_custom)) { String customFilterTimeUnit = getCustomTimeUnit(filterType); int customTimeNumber = getCustomTimeNumber(filterType); switch (customFilterTimeUnit) { case "Hours": c.add(Calendar.HOUR_OF_DAY, -1 * customTimeNumber); break; case "Days": c.add(Calendar.DAY_OF_MONTH, -1 * customTimeNumber); break; case "Months": c.add(Calendar.MONTH, -1 * customTimeNumber); break; default: c.add(Calendar.MINUTE, -1 * customTimeNumber); break; } } else { // no filter c = null; } if (c != null) { filter = new DateTimeFilter(c.getTime(), null, columnName); } return filter; } private String getFilterTitle() { if (getTimeFilterId() != getResources().getInteger(R.integer.time_filter_none) || getLocationTimeFilterId() != getResources().getInteger(R.integer.time_filter_none) || preferences.getBoolean(getResources().getString(R.string.activeImportantFilterKey), false) || preferences.getBoolean(getResources().getString(R.string.activeFavoritesFilterKey), false)) { return "Showing filtered results."; } else { return ""; } } private void hideKeyboard() { InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE); if (getActivity().getCurrentFocus() != null) { inputMethodManager.hideSoftInputFromWindow(getActivity().getCurrentFocus().getWindowToken(), 0); } } private void scheduleMarkerRefresh(RefreshMarkersRunnable task) { getView().postDelayed(task, task.intervalSeconds * 1000); } private class RefreshMarkersRunnable implements Runnable { private final PointCollection<?> points; private final String filterColumnName; private final String filterType; private final int timePeriodFilterId; private final int intervalSeconds; private RefreshMarkersRunnable(PointCollection<?> points, String filterColumnName, String filterType, int timePeriodFilterId, int intervalSeconds) { this.points = points; this.filterColumnName = filterColumnName; this.filterType = filterType; this.timePeriodFilterId = timePeriodFilterId; this.intervalSeconds = intervalSeconds; } public void run() { if (MapFragment.this.isDetached()) return; if (points.isVisible()) { points.refreshMarkerIcons(getTemporalFilter(filterColumnName, timePeriodFilterId, filterType)); } scheduleMarkerRefresh(this); } } }