Java tutorial
/* Copyright 2017 Esri * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.esri.arcgisruntime.sample.findplace; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import android.Manifest; import android.content.pm.PackageManager; import android.database.MatrixCursor; import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.os.Bundle; import android.provider.BaseColumns; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v4.widget.SimpleCursorAdapter; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.SearchView; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import com.esri.arcgisruntime.concurrent.ListenableFuture; import com.esri.arcgisruntime.geometry.Envelope; import com.esri.arcgisruntime.geometry.Geometry; import com.esri.arcgisruntime.geometry.Multipoint; import com.esri.arcgisruntime.geometry.Point; import com.esri.arcgisruntime.loadable.LoadStatus; import com.esri.arcgisruntime.mapping.ArcGISMap; import com.esri.arcgisruntime.mapping.Basemap; import com.esri.arcgisruntime.mapping.Viewpoint; import com.esri.arcgisruntime.mapping.view.Callout; import com.esri.arcgisruntime.mapping.view.DefaultMapViewOnTouchListener; import com.esri.arcgisruntime.mapping.view.Graphic; import com.esri.arcgisruntime.mapping.view.GraphicsOverlay; import com.esri.arcgisruntime.mapping.view.IdentifyGraphicsOverlayResult; import com.esri.arcgisruntime.mapping.view.LocationDisplay; import com.esri.arcgisruntime.mapping.view.MapView; import com.esri.arcgisruntime.mapping.view.ViewpointChangedEvent; import com.esri.arcgisruntime.mapping.view.ViewpointChangedListener; import com.esri.arcgisruntime.mapping.view.WrapAroundMode; import com.esri.arcgisruntime.symbology.PictureMarkerSymbol; import com.esri.arcgisruntime.tasks.geocode.GeocodeParameters; import com.esri.arcgisruntime.tasks.geocode.GeocodeResult; import com.esri.arcgisruntime.tasks.geocode.LocatorTask; import com.esri.arcgisruntime.tasks.geocode.SuggestParameters; import com.esri.arcgisruntime.tasks.geocode.SuggestResult; public class MainActivity extends AppCompatActivity { private final String[] reqPermissions = new String[] { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION }; private final String TAG = MainActivity.class.getSimpleName(); private final String COLUMN_NAME_ADDRESS = "address"; private final String[] mColumnNames = { BaseColumns._ID, COLUMN_NAME_ADDRESS }; private SearchView mPoiSearchView; private SearchView mProximitySearchView; private boolean mProximitySearchViewEmpty; private String mPoiAddress; private Point mPreferredSearchProximity; private MapView mMapView; private LocationDisplay mLocationDisplay; private LocatorTask mLocatorTask; private GraphicsOverlay mGraphicsOverlay; private SuggestParameters mPoiSuggestParameters; private GeocodeParameters mPoiGeocodeParameters; private SuggestParameters mProximitySuggestParameters; private GeocodeParameters mProximityGeocodeParameters; private PictureMarkerSymbol mPinSourceSymbol; private Geometry mCurrentExtentGeometry; private Callout mCallout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // if permissions are not already granted, request permission from the user if (!(ContextCompat.checkSelfPermission(MainActivity.this, reqPermissions[0]) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(MainActivity.this, reqPermissions[1]) == PackageManager.PERMISSION_GRANTED)) { int requestCode = 2; ActivityCompat.requestPermissions(MainActivity.this, reqPermissions, requestCode); } // setup the two SearchViews and show text hint mPoiSearchView = (SearchView) findViewById(R.id.poi_searchView); mPoiSearchView.setIconified(false); mPoiSearchView.setFocusable(false); mPoiSearchView.setQueryHint(getResources().getString(R.string.search_hint)); mProximitySearchView = (SearchView) findViewById(R.id.proximity_searchView); mProximitySearchView.setIconified(false); mProximitySearchView.setFocusable(false); mProximitySearchView.setQueryHint(getResources().getString(R.string.proximity_search_hint)); // setup redo search button Button redoSearchButton = (Button) findViewById(R.id.redo_search_button); // on redo button click call redoSearchInThisArea redoSearchButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { redoSearchInThisArea(); } }); // define pin drawable BitmapDrawable pinDrawable = (BitmapDrawable) ContextCompat.getDrawable(this, R.drawable.pin); try { mPinSourceSymbol = PictureMarkerSymbol.createAsync(pinDrawable).get(); } catch (InterruptedException | ExecutionException e) { String error = "Error creating PictureMarkerSymbol: " + e.getMessage(); Log.e(TAG, error); Toast.makeText(MainActivity.this, error, Toast.LENGTH_LONG).show(); } // set pin to half of native size mPinSourceSymbol.setWidth(19f); mPinSourceSymbol.setHeight(72f); // instantiate flag proximity search view flag mProximitySearchViewEmpty = true; // create a LocatorTask from an online service mLocatorTask = new LocatorTask(getString(R.string.world_geocode_service)); // inflate MapView from layout mMapView = (MapView) findViewById(R.id.mapView); // disable map wraparound mMapView.setWrapAroundMode(WrapAroundMode.DISABLED); // create a map with the BasemapType topographic final ArcGISMap map = new ArcGISMap(Basemap.createTopographic()); // set the map to be displayed in this view mMapView.setMap(map); // add listener to update extent when viewpoint has changed mMapView.addViewpointChangedListener(new ViewpointChangedListener() { @Override public void viewpointChanged(ViewpointChangedEvent viewpointChangedEvent) { // get the current map extent mCurrentExtentGeometry = mMapView.getCurrentViewpoint(Viewpoint.Type.BOUNDING_GEOMETRY) .getTargetGeometry(); } }); // add listener to handle callouts mMapView.setOnTouchListener(new DefaultMapViewOnTouchListener(this, mMapView) { @Override public boolean onSingleTapConfirmed(MotionEvent motionEvent) { showCallout(motionEvent); return true; } }); // setup and start location display mLocationDisplay = mMapView.getLocationDisplay(); mLocationDisplay.setAutoPanMode(LocationDisplay.AutoPanMode.RECENTER); mLocationDisplay.startAsync(); // initially use device location to focus POI search final Point[] currentLocation = new Point[1]; mLocationDisplay.addLocationChangedListener(new LocationDisplay.LocationChangedListener() { @Override public void onLocationChanged(LocationDisplay.LocationChangedEvent locationChangedEvent) { currentLocation[0] = mLocationDisplay.getMapLocation(); // only update preferredSearchLocation if device has moved if (!currentLocation[0].equals(mLocationDisplay.getMapLocation(), 100) || mPreferredSearchProximity == null) { mPreferredSearchProximity = mLocationDisplay.getMapLocation(); } } }); // define the graphics overlay mGraphicsOverlay = new GraphicsOverlay(); setupPoi(); setupProximity(); } /** * Sets up the POI SearchView. Uses MatrixCursor to show suggestions to the user as the user inputs text. */ private void setupPoi() { mPoiSuggestParameters = new SuggestParameters(); // filter categories for POI mPoiSuggestParameters.getCategories().add("POI"); mPoiGeocodeParameters = new GeocodeParameters(); // get all attributes mPoiGeocodeParameters.getResultAttributeNames().add("*"); mPoiSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String address) { // if proximity SearchView text box is empty, use the device location if (mProximitySearchViewEmpty) { mPreferredSearchProximity = mLocationDisplay.getMapLocation(); mProximitySearchView.setQuery("Using current location...", false); } // keep track of typed address mPoiAddress = address; // geocode typed address geoCodeTypedAddress(address); // clear focus from search views mPoiSearchView.clearFocus(); mProximitySearchView.clearFocus(); return true; } @Override public boolean onQueryTextChange(final String newText) { // as long as newText isn't empty, get suggestions from the locatorTask if (!newText.equals("")) { mPoiSuggestParameters.setSearchArea(mCurrentExtentGeometry); final ListenableFuture<List<SuggestResult>> suggestionsFuture = mLocatorTask .suggestAsync(newText, mPoiSuggestParameters); suggestionsFuture.addDoneListener(new Runnable() { @Override public void run() { try { // get the results of the async operation List<SuggestResult> suggestResults = suggestionsFuture.get(); if (!suggestResults.isEmpty()) { MatrixCursor suggestionsCursor = new MatrixCursor(mColumnNames); int key = 0; // add each poi_suggestion result to a new row for (SuggestResult result : suggestResults) { suggestionsCursor.addRow(new Object[] { key++, result.getLabel() }); } // define SimpleCursorAdapter String[] cols = new String[] { COLUMN_NAME_ADDRESS }; int[] to = new int[] { R.id.suggestion_address }; final SimpleCursorAdapter suggestionAdapter = new SimpleCursorAdapter( MainActivity.this, R.layout.suggestion, suggestionsCursor, cols, to, 0); mPoiSearchView.setSuggestionsAdapter(suggestionAdapter); // handle a poi_suggestion being chosen mPoiSearchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() { @Override public boolean onSuggestionSelect(int position) { return false; } @Override public boolean onSuggestionClick(int position) { // get the selected row MatrixCursor selectedRow = (MatrixCursor) suggestionAdapter .getItem(position); // get the row's index int selectedCursorIndex = selectedRow .getColumnIndex(COLUMN_NAME_ADDRESS); // get the string from the row at index mPoiAddress = selectedRow.getString(selectedCursorIndex); mPoiSearchView.setQuery(mPoiAddress, true); return true; } }); } else { mPoiAddress = newText; } } catch (Exception e) { Log.e(TAG, "Geocode suggestion error: " + e.getMessage()); } } }); } return true; } }); } /** * Sets up the proximity SearchView. Uses MatrixCursor to show suggestions to the user as the user inputs text. */ private void setupProximity() { mProximitySuggestParameters = new SuggestParameters(); mProximitySuggestParameters.getCategories().add("Populated Place"); mProximityGeocodeParameters = new GeocodeParameters(); // get all attributes mProximityGeocodeParameters.getResultAttributeNames().add("*"); mProximitySearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String address) { geoCodeTypedAddress(address); // clear focus from search views mPoiSearchView.clearFocus(); mProximitySearchView.clearFocus(); return true; } @Override public boolean onQueryTextChange(String newText) { // as long as newText isn't empty, get suggestions from the locatorTask if (!newText.equals("")) { mProximitySearchViewEmpty = false; final ListenableFuture<List<SuggestResult>> suggestionsFuture = mLocatorTask .suggestAsync(newText, mProximitySuggestParameters); suggestionsFuture.addDoneListener(new Runnable() { @Override public void run() { try { // get the list of suggestions List<SuggestResult> suggestResults = suggestionsFuture.get(); MatrixCursor suggestionsCursor = new MatrixCursor(mColumnNames); int key = 0; // add each SuggestResult to a new row for (SuggestResult result : suggestResults) { suggestionsCursor.addRow(new Object[] { key++, result.getLabel() }); } // define SimpleCursorAdapter String[] cols = new String[] { COLUMN_NAME_ADDRESS }; int[] to = new int[] { R.id.suggestion_address }; final SimpleCursorAdapter suggestionAdapter = new SimpleCursorAdapter( MainActivity.this, R.layout.suggestion, suggestionsCursor, cols, to, 0); mProximitySearchView.setSuggestionsAdapter(suggestionAdapter); mProximitySearchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() { @Override public boolean onSuggestionSelect(int position) { return false; } @Override public boolean onSuggestionClick(int position) { // get the selected row MatrixCursor selectedRow = (MatrixCursor) suggestionAdapter .getItem(position); // get the row's index int selectedCursorIndex = selectedRow.getColumnIndex(COLUMN_NAME_ADDRESS); // get the string from the row at index final String address = selectedRow.getString(selectedCursorIndex); mLocatorTask.addDoneLoadingListener(new Runnable() { @Override public void run() { if (mLocatorTask.getLoadStatus() == LoadStatus.LOADED) { // geocode the selected address to get location of address final ListenableFuture<List<GeocodeResult>> geocodeFuture = mLocatorTask .geocodeAsync(address, mProximityGeocodeParameters); geocodeFuture.addDoneListener(new Runnable() { @Override public void run() { try { // Get the results of the async operation List<GeocodeResult> geocodeResults = geocodeFuture .get(); if (geocodeResults.size() > 0) { // use geocodeResult to focus search area GeocodeResult geocodeResult = geocodeResults .get(0); // update preferred search area to the geocode result mPreferredSearchProximity = geocodeResult .getDisplayLocation(); mPoiGeocodeParameters.setSearchArea( mPreferredSearchProximity); // set the address string to the SearchView, but don't submit as a query mProximitySearchView.setQuery(address, false); // call POI search query mPoiSearchView.setQuery(mPoiAddress, true); // clear focus from search views mProximitySearchView.clearFocus(); mPoiSearchView.clearFocus(); } else { Toast.makeText(getApplicationContext(), getString(R.string.location_not_found) + address, Toast.LENGTH_LONG).show(); } } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "Geocode error: " + e.getMessage()); Toast.makeText(getApplicationContext(), getString(R.string.geo_locate_error), Toast.LENGTH_LONG).show(); } } }); } } }); return true; } }); } catch (Exception e) { Log.e(TAG, "Geocode suggestion error: " + e.getMessage()); } } }); // if search view is empty, set flag } else { mProximitySearchViewEmpty = true; } return true; } }); } /** * Performs a search for the POI listed in the SearchView, using the MapView's current extent to inform the search. */ private void redoSearchInThisArea() { // set center of current extent to preferred search proximity mPreferredSearchProximity = mCurrentExtentGeometry.getExtent().getCenter(); mPoiGeocodeParameters.setSearchArea(mCurrentExtentGeometry); mProximitySearchView.setQuery(getString(R.string.searching_by_area), false); // use most recent POI address geoCodeTypedAddress(mPoiAddress); } /** * Identifies the Graphic at the tapped point. Gets attribute of that Graphic and assigns it to a Callout, which is * then displayed. * * @param motionEvent from onSingleTapConfirmed */ private void showCallout(MotionEvent motionEvent) { // get the screen point android.graphics.Point screenPoint = new android.graphics.Point(Math.round(motionEvent.getX()), Math.round(motionEvent.getY())); // convert to map point final Point mapPoint = mMapView.screenToLocation(screenPoint); // from the graphics overlay, get graphics near the tapped location final ListenableFuture<IdentifyGraphicsOverlayResult> identifyResultsFuture = mMapView .identifyGraphicsOverlayAsync(mGraphicsOverlay, screenPoint, 10, false); identifyResultsFuture.addDoneListener(new Runnable() { @Override public void run() { try { IdentifyGraphicsOverlayResult identifyGraphicsOverlayResult = identifyResultsFuture.get(); List<Graphic> graphics = identifyGraphicsOverlayResult.getGraphics(); // if a graphic has been identified if (graphics.size() > 0) { //get the first graphic identified Graphic identifiedGraphic = graphics.get(0); // create a TextView for the Callout TextView calloutContent = new TextView(getApplicationContext()); calloutContent.setTextColor(Color.BLACK); // set the text of the Callout to graphic's attributes calloutContent.setText(identifiedGraphic.getAttributes().get("PlaceName").toString() + "\n" + identifiedGraphic.getAttributes().get("StAddr").toString()); // get Callout and set its options: animateCallout: true, recenterMap: false, animateRecenter: false mCallout = mMapView.getCallout(); mCallout.setShowOptions(new Callout.ShowOptions(true, false, false)); // set the leader position and show the callout mCallout.setLocation(identifiedGraphic.computeCalloutLocation(mapPoint, mMapView)); mCallout.setContent(calloutContent); mCallout.show(); } else { mCallout.dismiss(); } } catch (Exception e) { Log.e(TAG, "Identify error: " + e.getMessage()); } } }); } /** * Geocode an address passed in by the user. * * @param address read in from searchViews */ private void geoCodeTypedAddress(final String address) { // check that address isn't null if (address != null) { // POI geocode parameters set from proximity SearchView or, if empty, device location mPoiGeocodeParameters.setPreferredSearchLocation(mPreferredSearchProximity); mPoiGeocodeParameters.setSearchArea(mPreferredSearchProximity); // Execute async task to find the address mLocatorTask.addDoneLoadingListener(new Runnable() { @Override public void run() { if (mLocatorTask.getLoadStatus() == LoadStatus.LOADED) { // Call geocodeAsync passing in an address final ListenableFuture<List<GeocodeResult>> geocodeResultListenableFuture = mLocatorTask .geocodeAsync(address, mPoiGeocodeParameters); geocodeResultListenableFuture.addDoneListener(new Runnable() { @Override public void run() { try { // Get the results of the async operation List<GeocodeResult> geocodeResults = geocodeResultListenableFuture.get(); if (geocodeResults.size() > 0) { displaySearchResult(geocodeResults); } else { Toast.makeText(getApplicationContext(), getString(R.string.location_not_found) + address, Toast.LENGTH_LONG) .show(); } } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "Geocode error: " + e.getMessage()); Toast.makeText(getApplicationContext(), getString(R.string.geo_locate_error), Toast.LENGTH_LONG).show(); } } }); } else { Log.i(TAG, "Trying to reload locator task"); mLocatorTask.retryLoadAsync(); } } }); mLocatorTask.loadAsync(); } } /** * Turns a list of GeocodeResults into Points and adds them to a GraphicOverlay which is then drawn on the map. The * points are added to a multipoint used to calculate a viewpoint. * * @param geocodeResults as a list */ private void displaySearchResult(List<GeocodeResult> geocodeResults) { // dismiss any callout if (mMapView.getCallout() != null && mMapView.getCallout().isShowing()) { mMapView.getCallout().dismiss(); } // clear map of existing graphics mMapView.getGraphicsOverlays().clear(); mGraphicsOverlay.getGraphics().clear(); // create a list of points from the geocode results List<Point> resultPoints = new ArrayList<>(); for (GeocodeResult result : geocodeResults) { // create graphic object for resulting location Point resultPoint = result.getDisplayLocation(); Graphic resultLocGraphic = new Graphic(resultPoint, result.getAttributes(), mPinSourceSymbol); // add graphic to location layer mGraphicsOverlay.getGraphics().add(resultLocGraphic); resultPoints.add(resultPoint); } // add result points to a Multipoint and get an envelope surrounding it Multipoint resultsMultipoint = new Multipoint(resultPoints); Envelope resultsEnvelope = resultsMultipoint.getExtent(); // add a 25% buffer to the extent Envelope of result points Envelope resultsEnvelopeWithBuffer = new Envelope(resultsEnvelope.getCenter(), resultsEnvelope.getWidth() * 1.25, resultsEnvelope.getHeight() * 1.25); // zoom map to result over 3 seconds mMapView.setViewpointAsync(new Viewpoint(resultsEnvelopeWithBuffer), 3); // set the graphics overlay to the map mMapView.getGraphicsOverlays().add(mGraphicsOverlay); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { // if request is cancelled, the result arrays are empty if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { mLocationDisplay.startAsync(); } else { // if permission was denied, show toast to inform user what was chosen Toast.makeText(MainActivity.this, getResources().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(); } }